<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Shadowfacts</title>
    <link>https://shadowfacts.net</link>
    <description></description>
    <lastBuildDate>Tue, 16 Jan 2024 20:59:40 +0000</lastBuildDate>
    <item>
      <title>Parsing HTML Fast</title>
      <link>https://shadowfacts.net/2023/parsing-html-fast/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/parsing-html-fast/</guid>
      <pubDate>Wed, 27 Dec 2023 23:52:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>The urge to overengineer something recently befell me, and the target of that urge was the way Tusker handles HTML parsing. Mastodon provides the content of posts (and profile descriptions, and some other things) as HTML, which means that, in order to display them properly, you need to parse the HTML, a notoriously straightforward and easy task. For a long time, the approach I took was to use <a href="https://github.com/scinfu/SwiftSoup" data-link="github.com/scinfu/SwiftSoup">SwiftSoup</a> to parse the HTML and then walk the resulting node tree to build up a big <code>NSAttributedString</code> that could be displayed.</p>
<p>And while that technique worked perfectly well, there were a number of downsides that I wasn’t entirely satisfied with. First: SwiftSoup alone is a <em>big</em> dependency, at around ten thousand lines of code. Since it’s shipping in my app, I take the view that I’m responsible for making sure it doesn’t break—and that’s a lot of surface area to cover (moreover, since it was ported from a Java library, the code is extremely un-Swift-y). Second: it’s not especially fast. Partly because of its aforementioned nature as a Java port (everything is a class, lots of unnecessary copies), but primarily because it’s just doing more than I need it to (the first rule of optimization is do less work): it was parsing a string into a tree and then I was flattening that tree back into a string.</p>
<p>The <a href="/2022/swift-rust/" data-link="/2022/swift-rust/">last time</a> I needed to do something similar, I used lol-html, Cloudflare’s streaming HTML rewriter. But, while that worked (and is still in use for my RSS reader app), it’s written in Rust, and I didn’t want to introduce significant additional complexity to the build process for Tusker. So, writing a new HTML parser in Swift seemed like a great idea.</p>
<!-- excerpt-end -->
<h2 id="the-architecture"><a href="#the-architecture" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Architecture</h2>
<p>Parsing HTML is no small task, and <a href="https://html.spec.whatwg.org/multipage/parsing.html" data-link="html.spec.whatwg.org/multipage/parsing.h…">the spec</a> stands at some 42 thousand words. Fortunately, I get to ignore about half of that. Parsing HTML is divided into two primary stages: tokenizing and tree construction. The tokenization stage takes the character stream as input and produces a stream of tokens (text characters, comments, start/end tags, etc.). The tree construction stages then uses the token stream to build up the actual DOM tree. The main way I’m improving over SwiftSoup in terms of not doing unnecessary work is by skipping tree construction altogether.</p>
<p>This is based on lol-html’s architecture, which usually uses a simulated tree construction stage (because the tokenizer gets feedback from the tree constructor and may have its state altered based on the current position in the tree) and only switches to the slower, real one when necessary. In my case, though, I don’t even simulate tree construction, since everything I want from the output can be generated directly from the token stream.</p>
<p>This does mean that in certain circumstances the output of the tokenizer could be incorrect, but that’s not a problem for my use case. For Tusker, I’m only ever going to use it to parse fragments—not whole documents—and the Mastodon backend sanitizes incoming HTML. What’s more, from what I can tell of lol-html’s simulator, most of the cases where (without the tree construction stage) ambiguity arises or feedback is needed aren’t going to come up in my use case. They involve other tag namespaces (SVG, MathML), or things like <code>&lt;template&gt;</code> tags—both of which I’m comfortable with handling incorrectly.</p>
<h3 id="tokenizing"><a href="#tokenizing" class="header-anchor" aria-hidden="true" role="presentation">###</a> Tokenizing</h3>
<p>The tokenizer is the single biggest component of this project, weighing in at about 1500 lines of code. Fortunately though, the spec is quite clear. It describes in exacting detail the state machine that a spec-compliant tokenizer needs to mimic. To make things simple, all of my code is an almost one-for-one translation of the spec into Swift. There are a few places where I diverged, mostly in the name of performance, but where I didn’t, the code is quite easy to follow.</p>
<h3 id="attributed-strings"><a href="#attributed-strings" class="header-anchor" aria-hidden="true" role="presentation">###</a> Attributed Strings</h3>
<p>Converting the token stream to an <code>NSAttributedString</code> is somewhat more complicated, but not terribly so. The gist of it is that the converter tracks what the style of the string should be at the current point in the token stream. Then, when a text character arrives from the tokenizer, it’s added to a buffer representing characters for which the current styles apply (called a “run”). When an open/close tag token is emitted, the current style is adjusted. After the current styles change, the current run is finished and an attributed string is built for the current text and styles and is appended to the overall string.</p>
<h2 id="optimization"><a href="#optimization" class="header-anchor" aria-hidden="true" role="presentation">##</a> Optimization</h2><h3 id="named-character-references"><a href="#named-character-references" class="header-anchor" aria-hidden="true" role="presentation">###</a> Named Character References</h3>
<p>The spec requires HTML tokenizers to have some special handling for named character references (things like <code>&amp;amp;</code>) which aren’t well-formed (e.g., missing the trailing semicolon). Because I was sticking very closely to the spec, this was originally implemented rather inefficiently: when a named character reference began, it would consume further characters one by one while there were any named character references beginning with the accumulated characters. I was just using a regular Swift dictionary to store all of the valid character references, so this was effectively an O(n) operation for each character in the reference.</p>
<p>That is, to put it mildly, ridiculously inefficient. Instead, the new approach just consumes as many alphanumeric characters as possible until it hits a semicolon. Then, if there’s no valid character reference, it backs up character by character until it finds a valid reference. This optimizes for the common case of a properly-formed reference, but (as far as I can tell) preserves the error-handling behavior that the spec requires.</p>
<h3 id="static-dispatch"><a href="#static-dispatch" class="header-anchor" aria-hidden="true" role="presentation">###</a> Static Dispatch</h3>
<p>The way I implemented the tokenizer, there’s a separate method that follows the procedure for each state, each of which returns the next token. The tokenizer itself is also an <code>Iterator</code> that yields tokens. The <code>Iterator.next</code> implementation just switches over the current state and dispatches to the correct method.</p>
<p>It’s fairly common for the procedure for tokenizing to involve switching to a different state. Originally, I did this by changing the <code>state</code> and then calling <code>next()</code>. One small optimization I made relatively early was, in instances where the new state is constant, replacing the <code>next()</code> call with a direct call to the appropriate method for the new state—effectively statically dispatching whenever the state changes.</p>
<pre class="highlight" data-lang="swift"><code>state = .<span class="hl-prop">tagName</span>
<span class="hl-kw">return</span> <span class="hl-fn">next</span>()
<span class="hl-cmt">// vs.</span>
state = .<span class="hl-prop">tagName</span>
<span class="hl-kw">return</span> <span class="hl-fn">tokenizeTagName</span>()
</code></pre><h3 id="ui-nsfont-caching"><a href="#ui-nsfont-caching" class="header-anchor" aria-hidden="true" role="presentation">###</a> UI/NSFont Caching</h3>
<p>A number of the tags handled by the attributed string converter alter the current font. Originally, I implemented this by looking at the currently-applied styles and using them to assemble a <code>SymbolicTraits</code> that could be passed to <code>UIFontDescriptor.withSymbolicTraits</code> on the base font descriptor. When profiling, though, I found a non-trivial amount of time was being spent inside <code>withSymbolicTraits</code> to look up the actual font that should be used. So, the converter instead caches the known styles and their corresponding font objects.</p>
<h3 id="using-swift-array"><a href="#using-swift-array" class="header-anchor" aria-hidden="true" role="presentation">###</a> Using <code>Swift.Array</code></h3>
<p>One of the first optimizations I’d implemented was a type called <code>InlineArray3</code> which was a collection type that stored up to three elements inline. Growing beyond that would cause it to switch to a regular Swift array, the storage for which is out of line. The motivation for this was the fact that the vast majority of the HTML tags that the parser sees will have very few attributes, so you might be able to gain a little bit of performance by storing them inline.</p>
<p>After implementing a simple performance testing harness (discussed later), I went back and tested this, and it turned out that just using <code>Array</code> was a fair bit faster. Just goes to show that you shouldn’t try to optimize things without having concrete data.</p>
<h3 id="looping-in-hot-recursive-functions"><a href="#looping-in-hot-recursive-functions" class="header-anchor" aria-hidden="true" role="presentation">###</a> Looping in Hot Recursive Functions</h3>
<p>For a number of states in the tokenizer (such as while tokenizing a tag name), the state machines stays in the same state for multiple iterations before emitting a token. Initially, I implemented this just by having the tokenization function recursively call itself. One small but measurable performance improvement I got came from turning hot recursive paths into loops. So, rather than the function calling itself recursively, the entire function body would be wrapped in an infinite loop and the “recursive call” would simply continue on to the next loop iteration.</p>
<p>This was one of the more surprising optimizations I made, since I’d expected this to make no difference. Given tail-call optimization, I’d think both approaches would generate basically identical code. And indeed, looking at the disassembly before and after this change seems to confirm that. There are differences, and though they seem very minor, I guess they’re enough to make a difference (though a very, very small one—only about 5ms over 10k iterations).</p>
<h3 id="avoiding-enums-with-associated-values"><a href="#avoiding-enums-with-associated-values" class="header-anchor" aria-hidden="true" role="presentation">###</a> Avoiding Enums with Associated Values</h3>
<p>Lots of states of the tokenizer involve building up the current token until it’s ready to emit. This means the parser needs to store the in-progress token. My <code>Token</code> data type is a Swift enum with a handful of different cases. So, the natural way of representing the current token was as an instance variable of an optional <code>Token</code>. Modifying the current token means, then, pattern matching against a <code>Token</code> and then constructing the updated <code>Token</code> and assigning it back.</p>
<p>Unfortunately, this does not permit in-place modification, meaning that all of the enum’s associated values need to be copied. For primitive types, this might not be a problem. But for more complex types, like arrays and strings, this results in a bunch of extra copies. So, rather than having a single <code>currentToken</code> property, there are separate properties representing the data for each possible token—each of which can be modified in-place:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private var</span> currentToken: <span class="hl-type">Token</span>?
<span class="hl-cmt">// vs.</span>
<span class="hl-kw">private var</span> currentStartTag: (<span class="hl-type">String</span>, selfClosing: <span class="hl-type">Bool</span>, attributes: [<span class="hl-type">Attribute</span>])?
<span class="hl-kw">private var</span> currentEndTag: <span class="hl-type">String</span>?
<span class="hl-cmt">// etc.</span>
</code></pre><h3 id="using-unicode-scalar-instead-of-character"><a href="#using-unicode-scalar-instead-of-character" class="header-anchor" aria-hidden="true" role="presentation">###</a> Using <code>Unicode.Scalar</code> instead of <code>Character</code></h3>
<p>My tokenizer originally operated on a stream of Swift <code>Character</code>s, which represent extended grapheme clusters. The tokenizer, however, only cares about individual Unicode code points—and indeed is specified to operate on code points. So, I changed the tokenizer to operate on the <code>Unicode.Scalar</code> type. This resulted in a significant speed-up, since the there was no longer any time being spent on the grapheme breaking algorithm.</p>
<h3 id="grouping-character-tokens"><a href="#grouping-character-tokens" class="header-anchor" aria-hidden="true" role="presentation">###</a> Grouping Character Tokens</h3>
<p>When emitting the document text, the tokenizer is specified to emit individual characters. As an optimization, though, I introduced an additional token type which emits a whole string of characters in one go. Since long, uninterrupted runs of characters that don’t require further parsing are quite common in actual HTML documents, this eliminates a bunch of the overhead associated with handling a token that comes from switching back and forth between the tokenizer and whatever’s consuming the tokens (that overhead is amortized over the sequence of character tokens).</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>To both test the final performance of the new end-to-end HTML to <code>NSAttributedString</code> conversion process as well as guide most of the optimizations I described above, I wrote a small benchmarking harness that converts the HTML from the last ten thousand posts on my timeline to attributed strings using the old and new methods. This is basically the exact use case I have for this project, so I’m confident that the benchmarking results are fairly representative.</p>
<p>All told, the new process is about 2.7× faster when both are built in release mode, and a whopping 8× faster in debug (not terribly important, but nice for me since a debug build is often what’s on my phone).</p>
<p>I’m very happy with how this project turned out. I greatly enjoyed overengineering/optimizing something fairly self-contained, and the end result meets all of the goals/requirements I had for my use case. If you’re curious to see how it works in more detail, check out <a href="https://git.shadowfacts.net/shadowfacts/HTMLStreamer" data-link="git.shadowfacts.net/shadowfacts/HTMLStre…">the source code</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Passkey Sign In with Elixir and Phoenix</title>
      <link>https://shadowfacts.net/2023/phoenix-passkeys/</link>
      <category>elixir</category>
      <guid>https://shadowfacts.net/2023/phoenix-passkeys/</guid>
      <pubDate>Thu, 19 Oct 2023 15:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Passkeys are a replacement for passwords that use public/private cryptographic key pairs for login in a way that can be more user-friendly and resistant to a number of kinds of attacks. Let’s implement account registration and login with passkeys in a simple Phoenix web app.</p>
<p>This work is heavily based on <a href="https://www.imperialviolet.org/2022/09/22/passkeys.html" data-link="imperialviolet.org/2022/09/22/passkeys.h…">this article</a> by Adam Langley, which provides a great deal of information about implementing passkeys. My goal here is to fill in some more of the details, and provide some Elixir-specific information. As with Adam’s article, I’m not going to use any WebAuthn libraries (even though that may be advisable from a security/maintenance perspective) since I think it’s interesting and helpful to understand how things actually work.</p>
<p>Providing an exhaustive, production-ready implementation is a non-goal of this post. I’m going to make some slightly odd decisions for pedagogical reasons, and leave some things incomplete. That said, I’ll try to note when I’m doing so.</p>
<!-- excerpt-end -->
<p>To start, I’m using the default Phoenix template app (less Tailwind) in which I’ve also generated the default, controller-based <a href="https://hexdocs.pm/phoenix/mix_phx_gen_auth.html" data-link="hexdocs.pm/phoenix/mix_phx_gen_auth.html">authentication system</a>. Some parts of the password-specific stuff have been stripped out altogether, others will get changed to fit with the passkey authentication setup we’re going to build.</p>
<h2 id="database-schema"><a href="#database-schema" class="header-anchor" aria-hidden="true" role="presentation">##</a> Database schema</h2>
<p>The first thing we’ll need is a backend schema for passkeys. The only data we need to store for a particular passkey is a unique identifier, and the public key itself. We also want users to be able to have multiple passkeys associated with their account (since they may want to login from multiple platforms that don’t cross-sync passkeys), so the users schema will have a one-to-many relationship with the passkeys.</p>
<aside>
<p>This is one of those places I’m leaving things incomplete. What we’ll build will support having multiple passkeys tied to a single account, and let users log in with any of them. But I’m not going to implement the actual UI for adding additional ones—that’s left as an exercise for the reader ;)</p>
</aside>
<p>The actual model I’ll call <code>UserCredential</code>, since that’s closer to what the WebAuthn spec calls them. Here’s the schema, it’s pretty simple:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts.UserCredential</span> <span class="hl-kw">do</span>
  <span class="hl-kw">use</span> <span class="hl-mod">Ecto.Schema</span>
  <span class="hl-kw">import</span> <span class="hl-mod">Ecto.Changeset</span>

  <span class="hl-attr">@</span><span class="hl-attr">primary_key</span> {<span class="hl-str">:id</span>, <span class="hl-str">:binary</span>, []}

  <span class="hl-fn">schema</span> <span class="hl-str">&quot;users_credentials&quot;</span> <span class="hl-kw">do</span>
    <span class="hl-cmt"># DER-encoded Subject Public Key Info: https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.7</span>
    <span class="hl-fn">field</span> <span class="hl-str">:public_key_spki</span>, <span class="hl-str">:binary</span>
    <span class="hl-fn">belongs_to</span> <span class="hl-str">:user</span>, <span class="hl-mod">PhoenixPasskeys.Accounts.User</span>
    <span class="hl-fn">timestamps</span>()
  <span class="hl-kw">end</span>

  <span class="hl-kw">def</span> <span class="hl-fn">changeset</span>(<span class="hl-var">credential</span>, <span class="hl-var">attrs</span>) <span class="hl-kw">do</span>
    <span class="hl-var">credential</span>
    <span class="hl-op">|&gt;</span> <span class="hl-fn">cast</span>(<span class="hl-var">attrs</span>, [<span class="hl-str">:id</span>, <span class="hl-str">:public_key_spki</span>, <span class="hl-str">:user_id</span>])
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>There are a couple things to note here:</p>
<ol>
<li>First, we’re explicitly specifying that the primary key is a binary, since that’s what the WebAuthn API provides as the credential ID.</li>
<li>The <code>public_key_spki</code> field contains the data for the public key itself and the algorithm being used. We don’t care about the specific format, though, since we don’t have to parse it ourselves.</li>
</ol>
<p>To the generated <code>User</code> schema, I also added the <code>has_many</code> side of the relationship. There’s also a migration to create the credentials table. I won’t show it here, since it’s exactly what you’d expect—just make sure the <code>id</code> column is a binary.</p>
<h2 id="registration-java-script"><a href="#registration-java-script" class="header-anchor" aria-hidden="true" role="presentation">##</a> Registration JavaScript</h2>
<p>WebAuthn, being a modern web API, is a JavaScript API. This is a distinct disadvantage, if your users expect to be able to login from JavaScript-less browsers. Nonetheless, we proceed with the JS. Here’s the first bit:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;DOMContentLoaded&quot;</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-kw">const</span> <span class="hl-var">registrationForm</span> <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">&quot;registration-form&quot;</span>);
    <span class="hl-kw">if</span> (<span class="hl-var">registrationForm</span>) {
        <span class="hl-var">registrationForm</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;submit&quot;</span>, (<span class="hl-var">event</span>) <span class="hl-op">=&gt;</span> {
            <span class="hl-var">event</span>.<span class="hl-fn">preventDefault</span>();
            <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">registrationForm</span>);
        });
    }
});
</code></pre>
<p>If we find the registration <code>&lt;form&gt;</code> element (note that I’ve added an ID (and also removed the password field)), we add a submit listener to it which prevents the form submission and instead calls the <code>registerWebAuthnAccount</code> function we’ll create next.</p>
<p>Before we get there, we’ll also write a brief helper function to check whether passkeys are actually available:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">supportsPasskeys</span>() {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span><span class="hl-builtin">window</span>.<span class="hl-prop">PublicKeyCredential</span> <span class="hl-op">||</span> <span class="hl-op">!</span>PublicKeyCredential.<span class="hl-prop">isConditionalMediationAvailable</span>) {
		<span class="hl-kw">return</span> <span class="hl-const">false</span>;
	}
	<span class="hl-kw">const</span> [<span class="hl-var">conditional</span>, <span class="hl-var">userVerifiying</span>] <span class="hl-op">=</span> <span class="hl-kw">await</span> Promise.<span class="hl-fn">all</span>([
		PublicKeyCredential.<span class="hl-fn">isConditionalMediationAvailable</span>(),
		PublicKeyCredential.<span class="hl-fn">isUserVerifyingPlatformAuthenticatorAvailable</span>(),
	]);
	<span class="hl-kw">return</span> <span class="hl-var">conditional</span> <span class="hl-op">&amp;&amp;</span> <span class="hl-var">userVerifiying</span>;
}
</code></pre>
<p>The conditional mediation check establishes that WebAuthn conditional UI is available. This is what we’ll use for login, and is part of what makes using passkeys a nice, slick experience. Conditional UI will let us start a request for a passkey that doesn’t present anything until the user interacts with the browser’s passkey autofill UI.</p>
<p>The user-verifying platform authenticator check establishes that, well, there is a user-verifying platform authenticator. The platform authenticator part means an authenticator that’s part of the user’s device, not removable, and the user-verifying part means that the authenticator verifies the presence of the user (such as via biometrics).</p>
<p>In the <code>registerWebAuthnAccount</code> function, the first thing we need to do is check that both these conditions are met and passkeys are supported by the browser. If not, we’ll just bail out and registration won’t be possible.</p>
<aside>
<p>What you’d want to in an actual application depends on your circumstances. If you only want users to be able to register with passkeys, you should indicate that somehow, rather than failing silently as I’m doing here. Otherwise, you could fall back to an existing password-based registration flow.</p>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>(<span class="hl-kw">await</span> <span class="hl-fn">supportsPasskeys</span>())) {
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>Next, we’ll setup a big ol’ options object that we’ll pass to the WebAuthn API to specify what sort of credential we want. There’s going to be a whole lot of stuff here, so lets take it one piece at a time.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">createOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
			<span class="hl-prop">rp</span>: {
				<span class="hl-prop">id</span>: <span class="hl-str">&quot;localhost&quot;</span>,
				<span class="hl-prop">name</span>: <span class="hl-str">&quot;Phoenix Passkeys&quot;</span>,
			}, 
        },
    };
}
</code></pre>
<p>The RP is the Relying Party—that is, us, the party which relies on the credentials for authentication. The ID is the <a href="https://w3c.github.io/webauthn/#rp-id" data-link="w3c.github.io/webauthn/">domain of the RP</a>—just localhost for this example, though in reality you’d need to use your actual domain in production. The name is just a user-facing name for the RP.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">createOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">user</span>: {
                <span class="hl-prop">id</span>: <span class="hl-fn">generateUserID</span>(),
                <span class="hl-prop">name</span>: <span class="hl-var">form</span>[<span class="hl-str">&quot;user[email&quot;</span>].<span class="hl-prop">value</span>,
                <span class="hl-prop">displayName</span>: <span class="hl-str">&quot;&quot;</span>,
            },
        },
    };
}
</code></pre>
<p>Next up is some info about the user who the credential is for. The user’s “name” will just be the email that they entered in the form. The <code>displayName</code> value is required, per the spec, but we don’t have any other information so we just leave it blank and let the browser display the <code>name</code> only. The ID is where this gets a little weird, since we’re just generating a random 64-byte value:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">generateUserID</span>() {
	<span class="hl-kw">const</span> <span class="hl-var">userID</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(<span class="hl-num">64</span>);
	<span class="hl-var">crypto</span>.<span class="hl-fn">getRandomValues</span>(<span class="hl-var">userID</span>);
	<span class="hl-kw">return</span> <span class="hl-var">userID</span>;
}
</code></pre>
<p>If you were adding a passkey to an existing user account, you could use a server-specified value for the user ID and the browser would replace any existing credentials with the same user ID and RP ID. But, since the user is registering a new account at this point, we assume they don’t want to do that (and, moreover, we don’t have any ID we can use yet). The user ID will also be returned upon a successful login, which would let us look up the user that’s logging in. However, since we’re only going to allow a credential to belong to a single user, we can use the credential’s ID to uniquely determine the user.</p>
<p>Next up, we specify the types of public keys that we’ll accept. We’re going to support Ed25519, ES256, and RS256, since those are recommended by the WebAuthn spec:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">createOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">pubKeyCredParams</span>: [
                {<span class="hl-prop">type</span>: <span class="hl-str">&quot;public-key&quot;</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">8</span>}, <span class="hl-cmt">// Ed25519</span>
                {<span class="hl-prop">type</span>: <span class="hl-str">&quot;public-key&quot;</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">7</span>}, <span class="hl-cmt">// ES256</span>
                {<span class="hl-prop">type</span>: <span class="hl-str">&quot;public-key&quot;</span>, <span class="hl-prop">alg</span>: <span class="hl-op">-</span><span class="hl-num">257</span>}, <span class="hl-cmt">// RS256</span>
            ],
        },
    };
}
</code></pre>
<p>During the login process, the server will generate a challenge value that the client will sign and the server will verify was signed with the user’s private key. The spec also requires that we provide a challenge when creating a credential, but we’re not going to use it for anything (since the user is just now creating the credential, we have nothing trusted that we can verify the initial challenge against), so we just provide an empty value:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">createOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">challenge</span>: <span class="hl-kw">new</span> Uint8Array(),
        },
    };
}
</code></pre>
<p>Lastly, we give our requirements for authenticators that we want to use:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">createOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-cmt">// ...</span>
            <span class="hl-prop">authenticatorSelection</span>: {
                <span class="hl-prop">authenticatorAttachment</span>: <span class="hl-str">&quot;platform&quot;</span>,
                <span class="hl-prop">requireResidentKey</span>: <span class="hl-const">true</span>,
            },
        },
    };
}
</code></pre>
<p>We want only the platform authenticator, not anything else, like removable security keys. We also specify that we want a resident key. This usage of “resident” is deprecated terminology that’s enshrined in the API. Really it means we want a <em>discoverable</em> credential, so that the authenticator will surface it during the login process without us having to request the credential by ID. This is important to note, since it’s what prevents needing a separate username entry step. </p>
<p>Now that we (finally) have all the configuration options in place, we can actually proceed with the credential creation. We pass the options to <code>navigator.credentials.create</code> to actually create the WebAuthn credential. If that fails, we’ll just take the easy way out and alert to inform the user (in an actual service, you’d probably want better error handling).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">credential</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-var">navigator</span>.<span class="hl-prop">credentials</span>.<span class="hl-fn">create</span>(<span class="hl-var">createOptions</span>);
    <span class="hl-kw">if</span> (<span class="hl-op">!</span><span class="hl-var">credential</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Could not create credential&quot;</span>);
        <span class="hl-kw">return</span>
    } 
}
</code></pre>
<p>From the credential we get back, we need a few pieces of information. First is the decoded client data, which is an object that contains information about the credential creation request that occurred.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">clientDataJSON</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> TextDecoder().<span class="hl-fn">decode</span>(<span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-prop">clientDataJSON</span>);
    <span class="hl-kw">const</span> <span class="hl-var">clientData</span> <span class="hl-op">=</span> <span class="hl-const">JSON</span>.<span class="hl-fn">parse</span>(<span class="hl-var">clientDataJSON</span>);
}
</code></pre>
<p>The <code>clientDataJSON</code> field of the response object contains the JSON-serialized object in an <code>ArrayBuffer</code>, so we decode that to text and then parse the JSON. With the decoded object, we do a consistency check with a couple pieces of data: the type of the request, whether or not it was cross-origin, and the actual origin being used.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> (<span class="hl-var">clientData</span>.<span class="hl-prop">type</span> <span class="hl-op">!==</span> <span class="hl-str">&quot;webauthn.create&quot;</span> <span class="hl-op">||</span> ((<span class="hl-str">&quot;crossOrigin&quot;</span> <span class="hl-kw">in</span> <span class="hl-var">clientData</span>) <span class="hl-op">&amp;&amp;</span> <span class="hl-var">clientData</span>.<span class="hl-prop">crossOrigin</span>) <span class="hl-op">||</span> <span class="hl-var">clientData</span>.<span class="hl-prop">origin</span> <span class="hl-op">!==</span> <span class="hl-str">&quot;http://localhost:4000&quot;</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Invalid credential&quot;</span>);
        <span class="hl-kw">return</span>;
    } 
}
</code></pre>
<p>If it was not a creation request, the request was cross-origin, or the origin doesn’t match, we bail out. Note that in production, the origin should be checked against the actual production origin, not localhost. And again, in reality you’d want better error handling than just an alert.</p>
<p>Next, we need to get the authenticator data which is encoded in a binary format and pull a few pieces of data out of it. You can see the full format of the authenticator data <a href="https://w3c.github.io/webauthn/#sctn-authenticator-data" data-link="w3c.github.io/webauthn/">in the spec</a>, but the parts we’re interested in are the backed-up state and the credential ID, which is part of the <a href="https://w3c.github.io/webauthn/#attested-credential-data" data-link="w3c.github.io/webauthn/">attested credential data</a>.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">authenticatorData</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(<span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-fn">getAuthenticatorData</span>());
    <span class="hl-kw">const</span> <span class="hl-var">backedUp</span> <span class="hl-op">=</span> (<span class="hl-var">authenticatorData</span>[<span class="hl-num">32</span>] <span class="hl-op">&gt;&gt;</span> <span class="hl-num">4</span>) <span class="hl-op">&amp;</span> <span class="hl-num">1</span>;
    <span class="hl-kw">const</span> <span class="hl-var">idLength</span> <span class="hl-op">=</span> (<span class="hl-var">authenticatorData</span>[<span class="hl-num">53</span>] <span class="hl-op">&lt;&lt;</span> <span class="hl-num">8</span>) <span class="hl-op">|</span> <span class="hl-var">authenticatorData</span>[<span class="hl-num">54</span>];
    <span class="hl-kw">const</span> <span class="hl-var">id</span> <span class="hl-op">=</span> <span class="hl-var">authenticatorData</span>.<span class="hl-fn">slice</span>(<span class="hl-num">55</span>, <span class="hl-num">55</span> <span class="hl-op">+</span> <span class="hl-var">idLength</span>);
}
</code></pre>
<p>We get the backed-up bit from the flags byte at offset 32. Then we get the length of the credential ID, which is encoded as a big-endian, 16-bit integer at bytes 53 and 54. The ID itself immediately follows the length, thus starting at byte 55.</p>
<p>Before proceeding, we check that the <a href="https://w3c.github.io/webauthn/#sctn-credential-backup" data-link="w3c.github.io/webauthn/">backed-up bit</a> is set, indicating that the credential is backed-up and safe from the user losing the current device. If it’s not, we won’t let the user register with this passkey. I choose to do this since it’s recommended by Adam Langley’s blog post, but whether it’s actually necessary may depend on your specific circumstances.</p>
<aside>
<p>It seems like if we choose to block the user from registering with a passkey that’s already been generated, that passkey should be removed—since it won’t actually let them log in, and it may be confusing if it sticks around. But there doesn’t seem to be API to do that, so oh well.</p>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> (<span class="hl-var">backedUp</span> <span class="hl-op">!==</span> <span class="hl-num">1</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Can't register with non backed-up credential&quot;</span>);
        <span class="hl-kw">return</span>;
    } 
}
</code></pre>
<p>The last piece of data we need out of the credential is the actual public key:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">publicKey</span> <span class="hl-op">=</span> <span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-fn">getPublicKey</span>();
}
</code></pre>
<p>And with all that in place, we can actually initiate the registration. We’ll assemble a form data payload with all of the requisite values:</p>
<aside>
<details>
<summary>
There's also a simple helper function for converting the two ArrayBuffer values to base 64 (click me to expand).
</summary>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">buf</span>) {
	<span class="hl-kw">let</span> <span class="hl-var">binary</span> <span class="hl-op">=</span> <span class="hl-str">&quot;&quot;</span>;
	<span class="hl-kw">const</span> <span class="hl-var">bytes</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(<span class="hl-var">buf</span>);
	<span class="hl-kw">for</span> (<span class="hl-kw">let</span> <span class="hl-var">i</span> <span class="hl-op">=</span> <span class="hl-num">0</span>; <span class="hl-var">i</span> <span class="hl-op">&lt;</span> <span class="hl-var">buf</span>.<span class="hl-prop">byteLength</span>; <span class="hl-var">i</span><span class="hl-op">++</span>) {
		<span class="hl-var">binary</span> <span class="hl-op">+=</span> String.<span class="hl-fn">fromCharCode</span>(<span class="hl-var">bytes</span>[<span class="hl-var">i</span>]);
	}
	<span class="hl-kw">return</span> <span class="hl-fn">btoa</span>(<span class="hl-var">binary</span>);
}
</code></pre></details>
</aside>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">body</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> FormData();
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;_csrf_token&quot;</span>, <span class="hl-var">form</span>.<span class="hl-prop">_csrf_token</span>.<span class="hl-prop">value</span>);
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;email&quot;</span>, <span class="hl-var">form</span>[<span class="hl-str">&quot;user[email]&quot;</span>].<span class="hl-prop">value</span>);
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;credential_id&quot;</span>, <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">id</span>));
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;public_key_spki&quot;</span>, <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">publicKey</span>)); 
}
</code></pre>
<p>We’ll use the body in a POST request to the registration endpoint. The response we get back will contain a status value to indicate whether the request was successful or not. If it was successful, the backend will have set the session cookie, and we can redirect to the home page and the new user will be logged in. If registration failed, we’ll alert the user.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">form</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">resp</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(<span class="hl-var">form</span>.<span class="hl-prop">action</span>, {
        <span class="hl-prop">method</span>: <span class="hl-str">&quot;POST&quot;</span>,
        body,
    });
    <span class="hl-kw">const</span> <span class="hl-var">respJSON</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-var">resp</span>.<span class="hl-fn">json</span>();
    <span class="hl-kw">if</span> (<span class="hl-var">respJSON</span>.<span class="hl-prop">status</span> <span class="hl-op">===</span> <span class="hl-str">&quot;ok&quot;</span>) {
        <span class="hl-builtin">window</span>.<span class="hl-prop">location</span> <span class="hl-op">=</span> <span class="hl-str">&quot;/&quot;</span>;
    } <span class="hl-kw">else</span> {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Registration failed&quot;</span>);
    }
}
</code></pre>
<p>And with that, we’re done with JavaScript (for now) and can move on to the backend part of registering an account.</p>
<h2 id="creating-a-user"><a href="#creating-a-user" class="header-anchor" aria-hidden="true" role="presentation">##</a> Creating a user</h2>
<p>In the <code>UserRegistrationController</code> module that comes with the Phoenix auth template, we’ll change the <code>create</code> function. By default, it registers the user using the parameters from the signup form and then redirects to the homepage. Instead, we’re going to register using the passkey we created on the client and then respond with the JSON that our JavaScript is expecting.</p>
<p>The first thing we need to do is extract the values that were sent by the frontend and decode the base 64-encoded ones.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserRegistrationController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(<span class="hl-var">conn</span>, %{
        <span class="hl-str">&quot;email&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">email</span>,
        <span class="hl-str">&quot;credential_id&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">credential_id</span>,
        <span class="hl-str">&quot;public_key_spki&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">public_key_spki</span>
      }) <span class="hl-kw">do</span>
    <span class="hl-var">credential_id</span> <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(<span class="hl-var">credential_id</span>)
    <span class="hl-var">public_key_spki</span> <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(<span class="hl-var">public_key_spki</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Those get passed to the <code>Accounts.register_user</code> function, which we’ll update shortly to handle. If account creation succeeded, we’ll still send the confirmation email as the existing code did. After that, instead of redirecting, we’ll log the user in by setting the session cookie and then respond with the “ok” status for the frontend. If account creation fails, we’ll just respond with the “error” status so the frontend can alert the user.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserRegistrationController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
    <span class="hl-cmt"># ...</span>
    <span class="hl-kw">case</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">register_user</span>(<span class="hl-var">email</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
      {<span class="hl-str">:ok</span>, <span class="hl-var">user</span>} <span class="hl-op">-&gt;</span>
        <span class="hl-cmt"># Send confirmation email...</span>

        <span class="hl-var">conn</span>
        <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(<span class="hl-var">user</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})

      {<span class="hl-str">:error</span>, <span class="hl-cmt">_changeset</span>} <span class="hl-op">-&gt;</span>
        <span class="hl-fn">json</span>(<span class="hl-var">conn</span>, %{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Let’s update the <code>register_user</code> function. Instead of just creating a changeset for the user and then inserting it, we need to also create the authenticator. To avoid potentially leaving things in a broken state, we wrap both of these in a database transaction.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(<span class="hl-var">email</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      <span class="hl-var">user</span> <span class="hl-op">=</span>
        %<span class="hl-mod">User</span>{}
        <span class="hl-op">|&gt;</span> <span class="hl-mod">User</span><span class="hl-op">.</span><span class="hl-fn">registration_changeset</span>(%{<span class="hl-str">email: </span><span class="hl-var">email</span>})
        <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">insert</span>()
        <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
          {<span class="hl-str">:ok</span>, <span class="hl-var">user</span>} <span class="hl-op">-&gt;</span> <span class="hl-var">user</span>
          {<span class="hl-str">:error</span>, <span class="hl-var">changeset</span>} <span class="hl-op">-&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">rollback</span>(<span class="hl-var">changeset</span>)
        <span class="hl-kw">end</span>
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>First, we create a user with the given email. If the user creation fails, we abort and rollback the transaction. Then, we can create a credential belonging to the new user with the credential ID and public key we received from the client. As with the user, if creating the credential fails, we rollback the transaction.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(<span class="hl-var">email</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      <span class="hl-cmt"># ...</span>
      %<span class="hl-mod">UserCredential</span>{}
      <span class="hl-op">|&gt;</span> <span class="hl-mod">UserCredential</span><span class="hl-op">.</span><span class="hl-fn">changeset</span>(%{
        <span class="hl-str">id: </span><span class="hl-var">credential_id</span>,
        <span class="hl-str">public_key_spki: </span><span class="hl-var">public_key_spki</span>,
        <span class="hl-str">user_id: </span><span class="hl-var">user</span><span class="hl-op">.</span><span class="hl-fn">id</span>
      })
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">insert</span>()
      <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
        {<span class="hl-str">:ok</span>, <span class="hl-cmt">_credential</span>} <span class="hl-op">-&gt;</span> <span class="hl-const">nil</span>
        {<span class="hl-str">:error</span>, <span class="hl-var">changeset</span>} <span class="hl-op">-&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">rollback</span>(<span class="hl-var">changeset</span>)
      <span class="hl-kw">end</span>      
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>We don’t need to do anything with the newly created credential, so we can just ignore it once it’s been created.</p>
<p>And finally, from the transaction function, we return the user:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">register_user</span>(<span class="hl-var">email</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>(<span class="hl-kw">fn</span> <span class="hl-op">-&gt;</span>
      <span class="hl-cmt"># ...</span>
      <span class="hl-var">user</span>
    <span class="hl-kw">end</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The last thing we need to to complete the registration flow is to set the new user’s session cookie so that they’re logged in immediately. The <code>UserAuth</code> module that’s generated as part of the Phoenix auth template has a <code>log_in_user</code> function that does exactly this. But it also redirects the connection to another endpoint. We don’t want to do that, since we’re sending a JSON response, so I’ve split the function into two: one that only sets the session, and the existing function that sets the session and then redirects.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserAuth</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">log_in_user</span>(<span class="hl-var">conn</span>, <span class="hl-var">user</span>, <span class="hl-var">params</span> <span class="hl-op">\\</span> %{}) <span class="hl-kw">do</span>
    <span class="hl-var">user_return_to</span> <span class="hl-op">=</span> <span class="hl-fn">get_session</span>(<span class="hl-var">conn</span>, <span class="hl-str">:user_return_to</span>)
    <span class="hl-var">conn</span>
    <span class="hl-op">|&gt;</span> <span class="hl-fn">log_in_user_without_redirect</span>(<span class="hl-var">user</span>, <span class="hl-var">params</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">redirect</span>(<span class="hl-str">to: </span><span class="hl-var">user_return_to</span> <span class="hl-op">||</span> <span class="hl-fn">signed_in_path</span>(<span class="hl-var">conn</span>))
  <span class="hl-kw">end</span>

  <span class="hl-kw">def</span> <span class="hl-fn">log_in_user_without_redirect</span>(<span class="hl-var">conn</span>, <span class="hl-var">user</span>, <span class="hl-var">params</span> <span class="hl-op">\\</span> %{}) <span class="hl-kw">do</span>
    <span class="hl-var">token</span> <span class="hl-op">=</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">generate_user_session_token</span>(<span class="hl-var">user</span>)
    <span class="hl-var">conn</span>
    <span class="hl-op">|&gt;</span> <span class="hl-fn">renew_session</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_token_in_session</span>(<span class="hl-var">token</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">maybe_write_remember_me_cookie</span>(<span class="hl-var">token</span>, <span class="hl-var">params</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>And with that, everything is in place and you can now create an account with a passkey!</p>
<h2 id="login-form"><a href="#login-form" class="header-anchor" aria-hidden="true" role="presentation">##</a> Login form</h2>
<p>Now that the user’s got an account, they need to be able to login with it. That means once again interacting with the WebAuthn API and writing a bunch of JavaScript. But before we get there, we need some slight changes to the backend.</p>
<p>In the HTML for the login page, we’ll add an ID to the <code>&lt;form&gt;</code> element so that we can find it from JS. We’ll also remove the password field, which is obviously no longer necessary. Lastly, but certainly not least, we need to send a challenge to the client.</p>
<p>The challenge is a value that the user’s device will cryptographically sign with their private key. The result will get sent back to the server, and we’ll verify it against the public key we have stored thus authenticating them. We’ll send the challenge just in a hidden form field:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">input</span> <span class="hl-attr">type</span>=&quot;<span class="hl-str">hidden</span>&quot; <span class="hl-attr">id</span>=&quot;<span class="hl-str">challenge</span>&quot; <span class="hl-attr">value</span>=<span class="hl-str">{@webauthn_challenge}</span> /&gt;
</code></pre>
<p>In the session controller, we’ll need to generate and assign the challenge to the connection.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">new</span>(<span class="hl-var">conn</span>, <span class="hl-cmt">_params</span>) <span class="hl-kw">do</span>
    <span class="hl-var">conn</span>
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_webauthn_challenge</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-fn">render</span>(<span class="hl-str">:new</span>, <span class="hl-str">error_message: </span><span class="hl-const">nil</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>WebAuthn expects the challenge to be a value up to 64 bytes long, so we’ll use the Erlang crypto module to generate one of that length. The value is encoded as <a href="https://datatracker.ietf.org/doc/html/rfc4648#section-5" data-link="datatracker.ietf.org/doc/html/rfc4648">URL-safe base 64</a> (the same as normal base 64, but with dash and underscore rather than plus and slash) without padding. We encode it this way since that’s the format in which it will later be <a href="https://w3c.github.io/webauthn/#dom-collectedclientdata-challenge" data-link="w3c.github.io/webauthn/">returned</a> as part of the <code>clientDataJSON</code>, so when we extract that value we can directly compare it to the challenge value we generated.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">put_webauthn_challenge</span>(<span class="hl-var">conn</span>) <span class="hl-kw">do</span>
    <span class="hl-var">challenge</span> <span class="hl-op">=</span>
      <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">strong_rand_bytes</span>(<span class="hl-num">64</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">url_encode64</span>(<span class="hl-str">padding: </span><span class="hl-const">false</span>)

    <span class="hl-var">conn</span>
    <span class="hl-op">|&gt;</span> <span class="hl-fn">put_session</span>(<span class="hl-str">:webauthn_challenge</span>, <span class="hl-var">challenge</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-fn">assign</span>(<span class="hl-str">:webauthn_challenge</span>, <span class="hl-var">challenge</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Note that the challenge string is also stored in the session, so that we can later check that the challenge that the client signed matches the challenge we generated. It’s safe to store this in the session, even though it’s sent to the client, because the cookie is encrypted and signed so the client can’t tamper with it.</p>
<h2 id="login-java-script"><a href="#login-java-script" class="header-anchor" aria-hidden="true" role="presentation">##</a> Login JavaScript</h2>
<p>With the first part of the backend changes taken care of, it’s time for more JavaScript, baby!</p>
<p>We’ll follow a similar outline to the registration setup (and the same caveat applies about error handling).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;DOMContentLoaded&quot;</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">loginForm</span> <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">&quot;login-form&quot;</span>);
	<span class="hl-kw">if</span> (<span class="hl-var">loginForm</span>) { 
        <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>);
    }
});

<span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>) {
    <span class="hl-kw">if</span> (<span class="hl-op">!</span>(<span class="hl-kw">await</span> <span class="hl-fn">supportsPasskeys</span>())) {
        <span class="hl-kw">return</span>;
    }
}
</code></pre>
<p>The first thing we need to do is grab the challenge from the hidden form field, then we can construct the options object for getting the credential (which is, thankfully, much simpler than for creation).</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">challenge</span> <span class="hl-op">=</span> <span class="hl-var">loginForm</span>.<span class="hl-prop">challenge</span>.<span class="hl-prop">value</span>;
    <span class="hl-kw">const</span> <span class="hl-var">getOptions</span> <span class="hl-op">=</span> {
        <span class="hl-prop">mediation</span>: <span class="hl-str">&quot;conditional&quot;</span>,
        <span class="hl-prop">publicKey</span>: {
            <span class="hl-prop">challenge</span>: <span class="hl-fn">base64URLToArrayBuffer</span>(<span class="hl-var">challenge</span>),
            <span class="hl-prop">rpId</span>: <span class="hl-str">&quot;localhost&quot;</span>,
        },
    };
}
</code></pre><aside>
<details>
<summary>
Here are the little helper functions that handle decoding from both regular and URL-safe base 64.
</summary>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">function</span> <span class="hl-fn">base64URLToArrayBuffer</span>(<span class="hl-var">b64</span>) {
	<span class="hl-kw">const</span> <span class="hl-var">converted</span> <span class="hl-op">=</span> <span class="hl-var">b64</span>.<span class="hl-fn">replace</span>(<span class="hl-str"><span class="hl-op">/</span>[-_]<span class="hl-op">/</span>g</span>, (<span class="hl-var">c</span>) <span class="hl-op">=&gt;</span> <span class="hl-var">c</span> <span class="hl-op">===</span> <span class="hl-str">&quot;-&quot;</span> ? <span class="hl-str">&quot;+&quot;</span> : <span class="hl-str">&quot;/&quot;</span>);
	<span class="hl-kw">return</span> <span class="hl-fn">base64ToArrayBuffer</span>(<span class="hl-var">converted</span>);
}
<span class="hl-kw">function</span> <span class="hl-fn">base64ToArrayBuffer</span>(<span class="hl-var">b64</span>) {
	<span class="hl-kw">const</span> <span class="hl-var">bin</span> <span class="hl-op">=</span> <span class="hl-fn">atob</span>(<span class="hl-var">b64</span>);
	<span class="hl-kw">const</span> <span class="hl-var">bytes</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> Uint8Array(<span class="hl-var">bin</span>.<span class="hl-prop">length</span>);
	<span class="hl-kw">for</span> (<span class="hl-kw">let</span> <span class="hl-var">i</span> <span class="hl-op">=</span> <span class="hl-num">0</span>; <span class="hl-var">i</span> <span class="hl-op">&lt;</span> <span class="hl-var">bin</span>.<span class="hl-prop">length</span>; <span class="hl-var">i</span><span class="hl-op">++</span>) {
		<span class="hl-var">bytes</span>[<span class="hl-var">i</span>] <span class="hl-op">=</span> <span class="hl-var">bin</span>.<span class="hl-fn">charCodeAt</span>(<span class="hl-var">i</span>);
	}
	<span class="hl-kw">return</span> <span class="hl-var">bytes</span>.<span class="hl-prop">buffer</span>;
} 
</code></pre></details>
</aside>
<p>In the options, we specify that we want conditional mediation. As noted before, this means that the browser won’t display any UI, except for autofill, for this credential request until the user accepts the autofill suggestion. In the public key options, we also give the decoded challenge value and specify the our Relying Party ID (again, this would need to be the actual domain in production).</p>
<p>Now, we can actually make the credential request and then, if we get a credential back, encode and send all the values to the backend. We need to send the ID of the credential, so that the backend can find its public key and the corresponding user. We also need the client data JSON, which we send as text decoded from the <code>ArrayBuffer</code> it’s returned as. We also need to send the authenticator data as well as the signature itself.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">credential</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-var">navigator</span>.<span class="hl-prop">credentials</span>.<span class="hl-fn">get</span>(<span class="hl-var">getOptions</span>);
    <span class="hl-kw">if</span> (<span class="hl-op">!</span><span class="hl-var">credential</span>) {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Could not get credential&quot;</span>);
        <span class="hl-kw">return</span>;
    }

    <span class="hl-kw">const</span> <span class="hl-var">clientDataJSON</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> TextDecoder().<span class="hl-fn">decode</span>(<span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-prop">clientDataJSON</span>);

    <span class="hl-kw">const</span> <span class="hl-var">body</span> <span class="hl-op">=</span> <span class="hl-kw">new</span> FormData();
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;_csrf_token&quot;</span>, <span class="hl-var">loginForm</span>.<span class="hl-prop">_csrf_token</span>.<span class="hl-prop">value</span>);
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;raw_id&quot;</span>, <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">credential</span>.<span class="hl-prop">rawId</span>));
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;client_data_json&quot;</span>, <span class="hl-var">clientDataJSON</span>);
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;authenticator_data&quot;</span>, <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-prop">authenticatorData</span>));
    <span class="hl-var">body</span>.<span class="hl-fn">append</span>(<span class="hl-str">&quot;signature&quot;</span>, <span class="hl-fn">arrayBufferToBase64</span>(<span class="hl-var">credential</span>.<span class="hl-prop">response</span>.<span class="hl-prop">signature</span>));
}
</code></pre>
<p>We can then send a request to the login endpoint. If the backend request fails (or if we failed to get the credential) we just alert the user (again, you’d probably want something better in reality). If the login attempt was successful, the server will have set the session cookie, and so we can just redirect to the homepage and the user will be logged in.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">const</span> <span class="hl-var">resp</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(<span class="hl-str">&quot;/users/log_in&quot;</span>, {
        <span class="hl-prop">method</span>: <span class="hl-str">&quot;POST&quot;</span>,
        body,
    });
    <span class="hl-kw">const</span> <span class="hl-var">respJSON</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-var">resp</span>.<span class="hl-fn">json</span>();
    <span class="hl-kw">if</span> (<span class="hl-var">respJSON</span>?.<span class="hl-prop">status</span> <span class="hl-op">===</span> <span class="hl-str">&quot;ok&quot;</span>) {
        <span class="hl-builtin">window</span>.<span class="hl-prop">location</span> <span class="hl-op">=</span> <span class="hl-str">&quot;/&quot;</span>;
    } <span class="hl-kw">else</span> {
        <span class="hl-fn">alert</span>(<span class="hl-str">&quot;Login failed&quot;</span>);
    }
}
</code></pre>
<p>With that in place, let’s move on to the backend half of the login request.</p>
<h2 id="validating-a-login-attempt"><a href="#validating-a-login-attempt" class="header-anchor" aria-hidden="true" role="presentation">##</a> Validating a login attempt</h2>
<p>As with signup, we’ll modify the existing log in endpoint to actually validate the WebAuthn login attempt.</p>
<p>The first step is extracting all of the information the frontend provides in the params and decoding it:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(<span class="hl-var">conn</span>, <span class="hl-var">params</span>) <span class="hl-kw">do</span>
    <span class="hl-var">id</span> <span class="hl-op">=</span> <span class="hl-var">params</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">&quot;raw_id&quot;</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
    <span class="hl-var">authenticator_data</span> <span class="hl-op">=</span> <span class="hl-var">params</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">&quot;authenticator_data&quot;</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
    <span class="hl-var">client_data_json_str</span> <span class="hl-op">=</span> <span class="hl-var">params</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">&quot;client_data_json&quot;</span>)
    <span class="hl-var">signature</span> <span class="hl-op">=</span> <span class="hl-var">params</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-str">&quot;signature&quot;</span>) <span class="hl-op">|&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>()
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Next, we’re going to validate all of the information we got from the client. Before we get to that, there are a handful of helper functions we’ll use. First, looking up a credential by its ID:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">get_credential</span>(<span class="hl-var">id</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">get</span>(<span class="hl-mod">UserCredential</span>, <span class="hl-var">id</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">preload</span>(<span class="hl-str">:user</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Next, a function in the controller that verifies the signature against the provided data:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">verify_signature</span>(<span class="hl-var">credential</span>, <span class="hl-var">client_data_json_str</span>, <span class="hl-var">authenticator_data</span>, <span class="hl-var">signature</span>) <span class="hl-kw">do</span>
    <span class="hl-kw">with</span> {<span class="hl-str">:ok</span>, <span class="hl-var">pubkey</span>} <span class="hl-op">&lt;-</span> <span class="hl-mod">X509.PublicKey</span><span class="hl-op">.</span><span class="hl-fn">from_der</span>(<span class="hl-var">credential</span><span class="hl-op">.</span><span class="hl-fn">public_key_spki</span>),
         <span class="hl-var">client_data_json_hash</span> <span class="hl-op">&lt;-</span> <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">hash</span>(<span class="hl-str">:sha256</span>, <span class="hl-var">client_data_json_str</span>),
         <span class="hl-var">signed_message</span> <span class="hl-op">&lt;-</span> <span class="hl-var">authenticator_data</span> <span class="hl-op">&lt;&gt;</span> <span class="hl-var">client_data_json_hash</span>,
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-mod">:public_key</span><span class="hl-op">.</span><span class="hl-fn">verify</span>(<span class="hl-var">signed_message</span>, <span class="hl-str">:sha256</span>, <span class="hl-var">signature</span>, <span class="hl-var">pubkey</span>) <span class="hl-kw">do</span>
      <span class="hl-const">true</span>
    <span class="hl-kw">else</span>
      <span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
        <span class="hl-const">false</span>
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p><code>X509</code> comes from the <a href="https://hex.pm/packages/x509" data-link="hex.pm/packages/x509"><code>x509</code></a> package, which is the only third-party piece of code we’re using. It’s a fairly thin wrapper around the Erlang <code>public_key</code> and <code>crypto</code> modules, and mostly serves to save me from having to deal with Erlang records in my code. Its <code>from_der</code> helper function is used to parse the public key from the encoded format.</p>
<p>Next, we hash the client data JSON and append that hash to the authenticator data from the client. This value is what should match the signature using the public key we’ve got, so finally we check that. If all these steps succeeded, we return true, and false otherwise.</p>
<p>The last helper function will receive the decoded client data make sure it’s got all of the values that we expect. If the <code>crossOrigin</code> value is present and is not false, the client data is invalid and the login attempt will be rejected.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(%{<span class="hl-str">&quot;crossOrigin&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">crossOrigin</span>}) <span class="hl-kw">when</span> <span class="hl-var">crossOrigin</span> <span class="hl-op">!=</span> <span class="hl-const">false</span> <span class="hl-kw">do</span>
    <span class="hl-const">false</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Otherwise, we check that the data has the expected type and origin, and we extract the challenge value (note that we’re checking the origin again here, and this would need to change in production):</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(%{
         <span class="hl-str">&quot;type&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;webauthn.get&quot;</span>,
         <span class="hl-str">&quot;challenge&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">challenge</span>,
         <span class="hl-str">&quot;origin&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;http://localhost:4000&quot;</span>
       }) <span class="hl-kw">do</span>
    {<span class="hl-str">:ok</span>, <span class="hl-var">challenge</span>}
  <span class="hl-kw">end</span> 
<span class="hl-kw">end</span>
</code></pre>
<p>And lastly, if neither of the previous patterns matched, the client data fails validation:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">defp</span> <span class="hl-fn">check_client_data_json</span>(<span class="hl-cmt">_</span>), <span class="hl-str">do: </span><span class="hl-const">false</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Now, let’s put all those parts together and validate the login attempt.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">create</span>(<span class="hl-var">conn</span>, <span class="hl-var">params</span>) <span class="hl-kw">do</span>
    <span class="hl-cmt"># ...</span>
    <span class="hl-kw">with</span> <span class="hl-var">credential</span> <span class="hl-kw">when</span> <span class="hl-kw">not</span> <span class="hl-fn">is_nil</span>(<span class="hl-var">credential</span>) <span class="hl-op">&lt;-</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">get_credential</span>(<span class="hl-var">id</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">verify_signature</span>(<span class="hl-var">credential</span>, <span class="hl-var">client_data_json_str</span>, <span class="hl-var">authenticator_data</span>, <span class="hl-var">signature</span>),
         {<span class="hl-str">:ok</span>, <span class="hl-var">client_data_json</span>} <span class="hl-op">&lt;-</span> <span class="hl-mod">Jason</span><span class="hl-op">.</span><span class="hl-fn">decode</span>(<span class="hl-var">client_data_json_str</span>),
         {<span class="hl-str">:ok</span>, <span class="hl-var">challenge</span>} <span class="hl-op">&lt;-</span> <span class="hl-fn">check_client_data_json</span>(<span class="hl-var">client_data_json</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-var">challenge</span> <span class="hl-op">==</span> <span class="hl-fn">get_session</span>(<span class="hl-var">conn</span>, <span class="hl-str">:webauthn_challenge</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">part</span>(<span class="hl-var">authenticator_data</span>, <span class="hl-num">0</span>, <span class="hl-num">32</span>) <span class="hl-op">==</span> <span class="hl-mod">:crypto</span><span class="hl-op">.</span><span class="hl-fn">hash</span>(<span class="hl-str">:sha256</span>, <span class="hl-str">&quot;localhost&quot;</span>),
         <span class="hl-const">true</span> <span class="hl-op">&lt;-</span> (<span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-var">authenticator_data</span>, <span class="hl-num">32</span>) <span class="hl-op">&amp;&amp;&amp;</span> <span class="hl-num">1</span>) <span class="hl-op">==</span> <span class="hl-num">1</span> <span class="hl-kw">do</span>
      <span class="hl-var">conn</span>
      <span class="hl-op">|&gt;</span> <span class="hl-fn">delete_session</span>(<span class="hl-str">:webauthn_challenge</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(<span class="hl-var">credential</span><span class="hl-op">.</span><span class="hl-fn">user</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})
    <span class="hl-kw">else</span>
      <span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
        <span class="hl-fn">json</span>(<span class="hl-var">conn</span>, %{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Here’s everything that we’re doing:</p>
<ol>
<li>Lookup the credential with the given ID.</li>
<li>Use the public key we had stored to verify the signature on the authenticator data and client data JSON.</li>
<li>Decode the client data JSON.</li>
<li>Check that all the values in the client data are what we expect, and extract the challenge that was signed.</li>
<li>Ensure the challenge that the user signed matches what we previously generated.</li>
<li>Extract the hash of the origin from the <a href="https://w3c.github.io/webauthn/#authenticator-data" data-link="w3c.github.io/webauthn/">authenticator data</a> and ensure it matches our origin (this would not be localhost in production).</li>
<li>Check that the authenticator data has the user presence bit set (indicating that a person was actually present on the client’s end).</li>
</ol>
<p>If all of those steps succeeded, we remove the old challenge value from the session (since it’s no longer needed), actually log the user in, and then respond with the “ok” status that the JavaScript is expecting. If any step failed, we’ll respond with the “error” status and the frontend will alert the user.</p>
<p>Since there’s a lot going on here, it’s worth being clear about what exactly in this process lets us authenticate and prove that the user is who they claim to be. Since the signature verification step is using the public key that we stored during the registration process, we know that anyone that can produce a valid signature using that public key must be the user (or at any rate, have their private key). The value that they’re signing is, essentially, the challenge: a securely generated random value. The user isn’t directly signing the challenge, but this is still safe, since the challenge value is included in the client data JSON, that hash of which is included in the signed message.</p>
<p>So: the challenge value that was signed by the user must be in the client data, and the challenge value in the client data must be the one we generated. Given that, we know that the user whose key was used to sign the message is the one trying to log in now. That we’re verifying with the stored public key prevents an attacker from using an arbitrary key to sign the login attempt. And that the signed challenge matches the challenge the server generated means an attacker can’t reuse a previous response to login (a replay attack).</p>
<p>At long last, we finally have the ability to log in to our application using a passkey. Only a few minor things to go, so let’s forge ahead.</p>
<h2 id="handling-login-if-the-user-enters-an-email"><a href="#handling-login-if-the-user-enters-an-email" class="header-anchor" aria-hidden="true" role="presentation">##</a> Handling login if the user enters an email</h2>
<p>Although we’re presenting the conditional UI, there’s nothing preventing the user from typing their email into the field and then clicking “Sign in,” so we should probably handle that to. This can be done fairly simply by reusing our existing code for conditional login.</p>
<p>We’ll change the <code>loginWebAuthnAccount</code> to take an additional parameter, <code>conditional</code>, which will be a boolean indicating whether this login attempt is to setup the conditional UI or triggered by submitting the login form.</p>
<p>If it’s false, we won’t request conditional mediation and instead we’ll look up the credentials corresponding to the email the user entered and ask WebAuthn for one of those:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-kw">async</span> <span class="hl-kw">function</span> <span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>, <span class="hl-var">conditional</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">let</span> <span class="hl-var">allowCredentials</span> <span class="hl-op">=</span> [];
    <span class="hl-kw">if</span> (<span class="hl-op">!</span><span class="hl-var">conditional</span>) {
		<span class="hl-kw">const</span> <span class="hl-var">email</span> <span class="hl-op">=</span> <span class="hl-var">loginForm</span>[<span class="hl-str">&quot;user[email]&quot;</span>].<span class="hl-prop">value</span>;
		<span class="hl-kw">const</span> <span class="hl-var">resp</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-fn">fetch</span>(<span class="hl-str">`/users/log_in/credentials?email=<span class="hl-emb"><span class="hl-punct-sp">${</span><span class="hl-var">email</span><span class="hl-punct-sp">}</span></span>`</span>);
		<span class="hl-kw">const</span> <span class="hl-var">respJSON</span> <span class="hl-op">=</span> <span class="hl-kw">await</span> <span class="hl-var">resp</span>.<span class="hl-fn">json</span>();
		<span class="hl-var">allowCredentials</span> <span class="hl-op">=</span> <span class="hl-var">respJSON</span>.<span class="hl-fn">map</span>((<span class="hl-var">id</span>) <span class="hl-op">=&gt;</span> {
			<span class="hl-kw">return</span> {
				<span class="hl-prop">type</span>: <span class="hl-str">&quot;public-key&quot;</span>,
				<span class="hl-prop">id</span>: <span class="hl-fn">base64ToArrayBuffer</span>(<span class="hl-var">id</span>),
			};
		});
    }

    <span class="hl-kw">const</span> <span class="hl-var">getOptions</span> <span class="hl-op">=</span> {
		<span class="hl-prop">mediation</span>: <span class="hl-var">conditional</span> ? <span class="hl-str">&quot;conditional&quot;</span> : <span class="hl-str">&quot;optional&quot;</span>,
		<span class="hl-prop">publicKey</span>: {
			<span class="hl-prop">challenge</span>: <span class="hl-fn">base64URLToArrayBuffer</span>(<span class="hl-var">challenge</span>),
			<span class="hl-prop">rpId</span>: <span class="hl-str">&quot;localhost&quot;</span>,
			allowCredentials,
		}
    };
    <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The “optional” value for the <code>mediation</code> option means that the authenticator isn’t required to display UI, but will do so if its policies dictate that. The <code>allowCredentials</code> array contains objects describing all of the credentials that we want to accept—specifically, their binary IDs as <code>ArrayBuffer</code>s.</p>
<p>We look up the user’s credentials so that the one we actually request from the authenticator matches the account that the user is trying to log in with. To handle this, we’ll also wire up an additional route on the backend that returns the base 64-encoded IDs of all the credentials belonging to the user with a given email.</p>
<aside>
<p>This is a vector for user enumeration, which may or may not matter depending on your particular use case. If it does matter and you can’t do this, I would forego conditional UI entirely have the WebAuthn flow triggered by a plain button press or something.</p>
</aside>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserSessionController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">credentials</span>(<span class="hl-var">conn</span>, %{<span class="hl-str">&quot;email&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">email</span>}) <span class="hl-kw">do</span>
    <span class="hl-var">ids</span> <span class="hl-op">=</span>
      <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">get_credentials_by_email</span>(<span class="hl-var">email</span>)
      <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">map</span>(<span class="hl-kw">fn</span> <span class="hl-var">cred</span> <span class="hl-op">-&gt;</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">encode64</span>(<span class="hl-var">cred</span><span class="hl-op">.</span><span class="hl-fn">id</span>) <span class="hl-kw">end</span>)

    <span class="hl-fn">json</span>(<span class="hl-var">conn</span>, <span class="hl-var">ids</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The <code>get_credentials_by_email</code> function is quite simple. It just looks up a user by email, preloading any credentials they have and then returning them:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">get_credentials_by_email</span>(<span class="hl-var">email</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">one</span>(<span class="hl-fn">from</span> <span class="hl-var">u</span> <span class="hl-kw">in</span> <span class="hl-mod">User</span>, <span class="hl-str">where: </span><span class="hl-var">u</span><span class="hl-op">.</span><span class="hl-fn">email</span> <span class="hl-op">==</span> <span class="hl-op">^</span><span class="hl-var">email</span>, <span class="hl-str">preload: </span><span class="hl-str">:credentials</span>)
    <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
      %{<span class="hl-str">credentials: </span><span class="hl-var">credentials</span>} <span class="hl-op">-&gt;</span> <span class="hl-var">credentials</span>
      <span class="hl-const">nil</span> <span class="hl-op">-&gt;</span> []
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Back in the JS, we can tweak the setup code to pass <code>true</code> for the conditional parameter in the initial request and also register a submit handler on the login form that will invoke it with <code>false</code>:</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;DOMContentLoaded&quot;</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
	<span class="hl-kw">const</span> <span class="hl-var">loginForm</span> <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">&quot;login-form&quot;</span>);
	<span class="hl-kw">if</span> (<span class="hl-var">loginForm</span>) {
		<span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>, <span class="hl-const">true</span>);
		<span class="hl-var">loginForm</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;submit&quot;</span>, (<span class="hl-var">event</span>) <span class="hl-op">=&gt;</span> {
			<span class="hl-var">event</span>.<span class="hl-fn">preventDefault</span>();
			<span class="hl-fn">loginWebAuthnAccount</span>(<span class="hl-var">loginForm</span>, <span class="hl-const">false</span>);
		});
	}
});
</code></pre>
<p>And so we’ve handled the case where the user ignores the conditional UI and still types in their email to log in.</p>
<h2 id="passkey-reset"><a href="#passkey-reset" class="header-anchor" aria-hidden="true" role="presentation">##</a> Passkey reset</h2>
<p>A not-infrequent argument against passkeys is that if your phone falls into a lake, you lose access to all of your passkey-backed accounts. One could argue that this isn’t true because the definition of passkeys means that they’re backed up and thus protected from this sort of event (and indeed, earlier we only permitted registering with backed-up credentials). I think the argument isn’t particularly interesting, however, because you can still have a “Forgot my passkey” option that works just like it does now with passwords.</p>
<p>This is less secure than a passkey implementation that has no “Forgot” option. But it’s no less secure than current password-based systems, and I think the UX/security tradeoff here falls on the UX side—people will, inevitably, lose access to their passkeys while retaining access to their email.</p>
<p>Implementing isn’t too complicated, fortunately, since we can reuse much of the registration code. First, the JavaScript. The only change necessary is attaching the registration function to the reset form as well.</p>
<pre class="highlight" data-lang="js"><code><span class="hl-builtin">document</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;DOMContentLoaded&quot;</span>, () <span class="hl-op">=&gt;</span> {
    <span class="hl-cmt">// ...</span>
	<span class="hl-kw">const</span> <span class="hl-var">resetForm</span> <span class="hl-op">=</span> <span class="hl-builtin">document</span>.<span class="hl-fn">getElementById</span>(<span class="hl-str">&quot;reset-passkey-form&quot;</span>);
	<span class="hl-kw">if</span> (<span class="hl-var">resetForm</span>) {
		<span class="hl-var">resetForm</span>.<span class="hl-fn">addEventListener</span>(<span class="hl-str">&quot;submit&quot;</span>, (<span class="hl-var">event</span>) <span class="hl-op">=&gt;</span> {
			<span class="hl-var">event</span>.<span class="hl-fn">preventDefault</span>();
			<span class="hl-fn">registerWebAuthnAccount</span>(<span class="hl-var">resetForm</span>);
		});
	}
});
</code></pre>
<p>In the HTML for the reset form, we we need to include the email in the same form field as the registration form (and also add an ID to the <code>&lt;form&gt;</code> element):</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">input</span> <span class="hl-attr">type</span>=&quot;<span class="hl-str">hidden</span>&quot; <span class="hl-attr">name</span>=&quot;<span class="hl-str">user[email]</span>&quot; <span class="hl-attr">value</span>=<span class="hl-str">{@user.email}</span> /&gt;
</code></pre>
<p>In the function for the reset route (which is changed to POST from PUT, to match the signup route), we take the credential ID and public key and use them to update the user’s credentials:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeysWeb.UserResetPasswordController</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">update</span>(<span class="hl-var">conn</span>, %{
        <span class="hl-str">&quot;credential_id&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">credential_id</span>,
        <span class="hl-str">&quot;public_key_spki&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-var">public_key_spki</span>
      }) <span class="hl-kw">do</span>
    <span class="hl-var">credential_id</span> <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(<span class="hl-var">credential_id</span>)
    <span class="hl-var">public_key_spki</span> <span class="hl-op">=</span> <span class="hl-mod">Base</span><span class="hl-op">.</span><span class="hl-fn">decode64!</span>(<span class="hl-var">public_key_spki</span>)
    <span class="hl-kw">case</span> <span class="hl-mod">Accounts</span><span class="hl-op">.</span><span class="hl-fn">reset_user_credentials</span>(<span class="hl-var">conn</span><span class="hl-op">.</span><span class="hl-fn">assigns</span><span class="hl-op">.</span><span class="hl-fn">user</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
      <span class="hl-str">:ok</span> <span class="hl-op">-&gt;</span>
        <span class="hl-var">conn</span>
        <span class="hl-op">|&gt;</span> <span class="hl-fn">put_flash</span>(<span class="hl-str">:info</span>, <span class="hl-str">&quot;Passkey reset successfully.&quot;</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-mod">UserAuth</span><span class="hl-op">.</span><span class="hl-fn">log_in_user_without_redirect</span>(<span class="hl-var">conn</span><span class="hl-op">.</span><span class="hl-fn">assigns</span><span class="hl-op">.</span><span class="hl-fn">user</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:ok</span>})

      {<span class="hl-str">:error</span>, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span>
        <span class="hl-var">conn</span>
        <span class="hl-op">|&gt;</span> <span class="hl-fn">put_flash</span>(<span class="hl-str">:error</span>, <span class="hl-str">&quot;Error resetting passkey&quot;</span>)
        <span class="hl-op">|&gt;</span> <span class="hl-fn">json</span>(%{<span class="hl-str">status: </span><span class="hl-str">:error</span>})
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>After updating, we log the user in if applicable and return JSON with the appropriate status.</p>
<p>The <code>reset_user_credentials</code> function works very similarly to the original reset password function that was part of the template: it deletes all the user’s existing sessions, and then removes their existing credentials and creates a new one:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defmodule</span> <span class="hl-mod">PhoenixPasskeys.Accounts</span> <span class="hl-kw">do</span>
  <span class="hl-kw">def</span> <span class="hl-fn">reset_user_credentials</span>(<span class="hl-var">user</span>, <span class="hl-var">credential_id</span>, <span class="hl-var">public_key_spki</span>) <span class="hl-kw">do</span>
    <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">new</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">delete_all</span>(
      <span class="hl-str">:old_credentials</span>,
      <span class="hl-fn">from</span>(<span class="hl-var">a</span> <span class="hl-kw">in</span> <span class="hl-mod">UserCredential</span>, <span class="hl-str">where: </span><span class="hl-var">a</span><span class="hl-op">.</span><span class="hl-fn">user_id</span> <span class="hl-op">==</span> <span class="hl-op">^</span><span class="hl-var">user</span><span class="hl-op">.</span><span class="hl-fn">id</span>)
    )
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">insert</span>(
      <span class="hl-str">:new_credential</span>,
      <span class="hl-mod">UserCredential</span><span class="hl-op">.</span><span class="hl-fn">changeset</span>(%<span class="hl-mod">UserCredential</span>{}, %{
        <span class="hl-str">id: </span><span class="hl-var">credential_id</span>,
        <span class="hl-str">public_key_spki: </span><span class="hl-var">public_key_spki</span>,
        <span class="hl-str">user_id: </span><span class="hl-var">user</span><span class="hl-op">.</span><span class="hl-fn">id</span>
      })
    )
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Ecto.Multi</span><span class="hl-op">.</span><span class="hl-fn">delete_all</span>(<span class="hl-str">:tokens</span>, <span class="hl-mod">UserToken</span><span class="hl-op">.</span><span class="hl-fn">user_and_contexts_query</span>(<span class="hl-var">user</span>, <span class="hl-str">:all</span>))
    <span class="hl-op">|&gt;</span> <span class="hl-mod">Repo</span><span class="hl-op">.</span><span class="hl-fn">transaction</span>()
    <span class="hl-op">|&gt;</span> <span class="hl-kw">case</span> <span class="hl-kw">do</span>
      {<span class="hl-str">:ok</span>, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span> <span class="hl-str">:ok</span>
      {<span class="hl-str">:error</span>, <span class="hl-cmt">_</span>, <span class="hl-var">changeset</span>, <span class="hl-cmt">_</span>} <span class="hl-op">-&gt;</span> {<span class="hl-str">:error</span>, <span class="hl-var">changeset</span>}
    <span class="hl-kw">end</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>It’s worth noting that this does have slightly weaker security properties than the <code>phx.gen.auth</code> reset password implementation. With that approach, leaking a password reset token does not necessarily result in an account takeover, since whoever obtained the leaked token may not know the target user’s email address. Since the auth template forces the user to re-login after a reset, this prevents someone without the email address from gaining access even if they change the password.</p>
<p>But since logging in with a passkey is functionally a single factor, resetting it means gaining access to the account. So, a leaked reset token gives the bearer control over the account. This is an argument against having a reset option, but whether this is a concern in practice depends on your specific circumstances.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>As noted, this is not a complete implementation. There are a handful of places where I’ve left things unfinished since this isn’t meant to be production-level code. There are also a few places where there are security decisions that need to be made on a more contextual basis, that I’ve tried to note. And, of course, you wouldn’t really want to only permit signing in with passkeys and wholesale drop the passwords column from your database. </p>
<p>Nonetheless, I hope this has been a helpful look at how to implement passkeys in an Elixir/Phoenix application. You can find the complete repo <a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys" data-link="git.shadowfacts.net/shadowfacts/phoenix_…">here</a>, and it may also be useful to look at the <a href="https://git.shadowfacts.net/shadowfacts/phoenix_passkeys/commit/ce4f485dbc4e528bdd13a57b0d379012dd893338" data-link="git.shadowfacts.net/shadowfacts/phoenix_…">specific commit</a> where passkey support was added.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Theming iOS Apps is No Longer Hard</title>
      <link>https://shadowfacts.net/2023/custom-traits/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/custom-traits/</guid>
      <pubDate>Tue, 03 Oct 2023 02:37:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Well, at least not for the <a href="/2023/theming-ios-apps/" data-link="/2023/theming-ios-apps/">same reasons</a>. I figured I’d write a brief follow-up post, but unless you’ve been living under a rock, you’ll have heard that UIKit gained support for custom traits in <code>UITraitCollection</code> with iOS 17. They work very similarly to SwiftUI’s environment and are exactly what I wanted.</p>
<!-- excerpt-end -->
<p>To create a custom trait, you define a type conforming to <code>UITraitDefinition</code> which serves as the key for your trait:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> PureBlackDarkModeTrait: <span class="hl-type">UITraitDefinition</span> {
    <span class="hl-kw">static let</span> defaultValue = <span class="hl-kw">true
    static let</span> affectsColorAppearance = <span class="hl-kw">true</span>
}
</code></pre>
<p>The default value is, well, the default value. That is, what will be read when the trait’s not explicitly defined in the trait environment. The trait definition also specifies whether this trait can effect the appearance of colors, meaning whether dynamic <code>UIColor</code>s should be reëvaluted when the trait’s value changes.</p>
<p>Then, you can define an extension on <code>UITraitCollection</code> which provides more idiomatic access to the trait (rather than always explicitly looking it up with the type):</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">UITraitCollection</span> {
    <span class="hl-kw">var</span> pureBlackDarkMode: <span class="hl-type">Bool</span> {
        <span class="hl-kw">self</span>[<span class="hl-type">PureBlackDarkModeTrait</span>.<span class="hl-kw">self</span>]
    }
}
</code></pre>
<p>One place where UIKit differs slightly from SwiftUI is that trait setters are defined on a separate type: <code>UIMutableTraits</code>. So, one more extension:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">UIMutableTraits</span> {
    <span class="hl-kw">var</span> pureBlackDarkMode: <span class="hl-type">Bool</span> {
        <span class="hl-kw">get</span> { <span class="hl-kw">self</span>[<span class="hl-type">PureBlackDarkModeTrait</span>.<span class="hl-kw">self</span>] }
        <span class="hl-kw">set</span> { <span class="hl-kw">self</span>[<span class="hl-type">PureBlackDarkModeTrait</span>.<span class="hl-kw">self</span>] = newValue }
    }
}
</code></pre>
<p>And with that, I can continue using my custom trait just as I was prior to iOS 17, but without any of the pile of hacks. Reading the trait from a <code>UIColor</code>’s <code>dynamicProvider</code> closure works exactly as you expect and updates when appropriate.</p>
<aside class="inline">
<p>Another nice hack-eliminating change is the ability to override traits at any point in the view hierarchy, rather than just on view controllers and presentation controllers. So, to actually set this trait for my entire app, I just set it on the root <code>UIWindow</code>’s <code>traitOverrides</code> and it propagates downward—rather than having to use SPI to get the root presentation controller and modify it’s override trait collection.</p>
</aside>
<p>It’s a pretty minor thing, objectively speaking, but this is a strong contender for my favorite feature of iOS 17.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A Hero View Controller Transition in SwiftUI</title>
      <link>https://shadowfacts.net/2023/swiftui-hero-transition/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/swiftui-hero-transition/</guid>
      <pubDate>Mon, 22 May 2023 03:40:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Out of the box, SwiftUI has a <a href="https://developer.apple.com/documentation/swiftui/view/matchedgeometryeffect(id:in:properties:anchor:issource:)" data-link="developer.apple.com/documentation/swiftu…"><code>matchedGeometryEffect</code></a> modifier that makes it relatively easy to build hero transitions (a style of transition where a new screen is presented and part of the source screen changes position and size to reach it’s place on the new screen). It’s cool that SwiftUI includes this out of the box, but unfortunately it has a few limitations that make it unsuitable for certain use cases. Particularly for me, that it doesn’t work with presenting another view. Most examples on the internet<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> work around this by faking a custom presented view: just slap a full-screen background color down and show your content on top of it. That’s essentially the same as a presenting a full-screen view, with the one major caveat that the pseudo-presented view can only cover the containing hosting controller. And if that controller isn’t full-screen (say, if it’s presented as a sheet), you can’t present anything that’s truly full-screen. So, let’s build a custom hero transition that actually presents it’s content across the entire screen. </p>
<!-- excerpt-end -->
<h2 id="presenting-a-uiview-controller"><a href="#presenting-a-uiview-controller" class="header-anchor" aria-hidden="true" role="presentation">##</a> Presenting a <code>UIViewController</code></h2>
<p>The first problem we need to contend with is how to bust out of the containing <code>UIHostingController</code> from within the SwiftUI view tree. In UIKit-land, we’d “escape” the current view controller by presenting another one. SwiftUI has presentation modifiers like <code>sheet</code> and <code>fullScreenCover</code>, which are analogous to the UIKit <code>present(_:animated:)</code> and <code>modalPresentationStyle</code> APIs, but the builtin SwiftUI API doesn’t work for us since the whole point of this is controlling the presentation animation, which SwiftUI doesn’t expose.</p>
<p>So, to get at the presentation animation APIs, we need to present the VC ourselves. Which means that from inside SwiftUI, we need to have access to a view controller whose <code>present</code> method we can call. Rather than trying to walk up the view tree to find the <code>UIHostingController</code> that contains the SwiftUI view tree, we can use <code>UIViewControllerRepresentable</code> to create our own VC and let the framework manage the child VC relationship. We can still call <code>present</code>, because UIKit will handle forwarding the presentation up the hierarchy until it finds one that can actually handle it.</p>
<p>The representable will take a function that creates a view controller—so we can defer its creation until it’s actually used—as well as a <code>Binding&lt;Bool&gt;</code> representing whether the VC is currently presented, following the SwiftUI pattern for presentations.</p>
<p>The actual view controller that the representable, uh, represents will be completely empty and unconfigured: we only need it to exist so that we can call <code>present</code>, we don’t need it to display anything.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ViewControllerPresenter: <span class="hl-type">UIViewControllerRepresentable</span> {
    <span class="hl-kw">let</span> makeVC: () -&gt; <span class="hl-type">UIViewController</span>
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    
    <span class="hl-kw">func</span> makeUIViewController(context: <span class="hl-type">Context</span>) -&gt; <span class="hl-type">UIViewController</span> {
        <span class="hl-kw">return</span> <span class="hl-type">UIViewController</span>()
    }
    
    <span class="hl-kw">func</span> updateUIViewController(<span class="hl-kw">_</span> uiViewController: <span class="hl-type">UIViewController</span>, context: <span class="hl-type">Context</span>) {
    }
}
</code></pre>
<p>In the update method, we can read the value of the binding and present or dismiss the VC as necessary.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> updateUIViewController(<span class="hl-kw">_</span> uiViewController: <span class="hl-type">UIViewController</span>, context: <span class="hl-type">Context</span>) {
    <span class="hl-kw">if</span> isPresented {
        <span class="hl-kw">if</span> uiViewController.<span class="hl-prop">presentedViewController</span> == <span class="hl-kw">nil</span> {
            <span class="hl-kw">let</span> presented = <span class="hl-fn">makeVC</span>()
            uiViewController.<span class="hl-fn">present</span>(presented, animated: <span class="hl-kw">true</span>)
        }
    } <span class="hl-kw">else</span> {
        <span class="hl-kw">if let</span> presentedViewController = uiViewController.<span class="hl-prop">presentedViewController</span>,
           !presentedViewController.<span class="hl-fn">isBeingDismissed</span> {
			uiViewController.<span class="hl-fn">dismiss</span>(animated: <span class="hl-kw">true</span>)
        }
    }
}
</code></pre>
<p>A couple things of note:</p>
<ol>
<li><code>makeVC</code> is called only once, when the VC is first presented. This means that, while it’s okay to access SwiftUI state in when constructing the VC, updates to the state will not be reflected in the VC, so we have to take care to either pass bindings or store mutable state inside of an immutable container (i.e., a class).</li>
<li>We make sure to check <code>isBeingDismissed</code> before dismissing the presented VC. Otherwise, if there’s another view update during the dismissal—which is entirely possible—we’ll double call <code>dismiss</code> and potentially dismiss the VC containing the presenter representable.</li>
</ol>
<p>Just this already works pretty well: you can present and dismiss a view controller using a SwiftUI binding. But the view controller also needs to be able to dismiss itself, without having any knowledge of the binding, and still keep the binding value up-to-date. To accomplish that, we’ll make the coordinator the delegate of the presentation controller, so it can use the will-dismiss method to update the binding state.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> updateUIViewController(<span class="hl-kw">_</span> uiViewController: <span class="hl-type">UIViewController</span>, context: <span class="hl-type">Context</span>) {
    <span class="hl-kw">if</span> isPresented {
        <span class="hl-kw">if</span> uiViewController.<span class="hl-prop">presentedViewController</span> == <span class="hl-kw">nil</span> {
            <span class="hl-kw">let</span> presented = <span class="hl-fn">makeVC</span>()
            presented.<span class="hl-prop">presentationController</span>!.delegate = context.<span class="hl-prop">coordinator</span>
            uiViewController.<span class="hl-fn">present</span>(presented, animated: <span class="hl-kw">true</span>)
        }
    } <span class="hl-kw">else</span> {
        <span class="hl-cmt">// ...</span>
    }
}

<span class="hl-kw">func</span> makeCoordinator() -&gt; <span class="hl-type">Coordinator</span> {
    <span class="hl-kw">return</span> <span class="hl-type">Coordinator</span>(isPresented: <span class="hl-prop">$isPresented</span>)
}

<span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UIAdaptivePresentationControllerDelegate</span> {
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    
    <span class="hl-kw">init</span>(isPresented: <span class="hl-type">Binding</span>&lt;<span class="hl-type">Bool</span>&gt;) {
        <span class="hl-kw">self</span>.<span class="hl-prop">_isPresented</span> = isPresented
    }
    
    <span class="hl-kw">func</span> presentationControllerWillDismiss(<span class="hl-kw">_</span> presentationController: <span class="hl-type">UIPresentationController</span>) {
        isPresented = <span class="hl-kw">false</span>
    }
}
</code></pre>
<p>Lastly, if you try this combined with another presented VC, you’ll notice a rather annoying problem: a view update of the presenter representable when another VC is presented results in that VC getting dismissed. Because the <code>presentedViewController</code> doesn’t just look at the target’s presented VC, but walks up the VC hierarchy until it finds the actual VC responsible for presentation. So if another VC was presented, it will be returned and, since <code>isPresented</code> is false, be dismissed prematurely. To solve this, we can keep track of when the representable itself actually triggered the presentation.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UIAdaptivePresentationControllerDelegate</span> {
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    <span class="hl-kw">var</span> didPresent = <span class="hl-kw">false</span>
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">func</span> presentationControllerWillDismiss(<span class="hl-kw">_</span> presentationController: <span class="hl-type">UIPresentationController</span>) {
        isPresented = <span class="hl-kw">false</span>
        didPresent = <span class="hl-kw">false</span>
    }
}
</code></pre>
<p>This doesn’t use a SwiftUI state property, since we’re deliberately going to change it during view updates. When the representable presents the VC, it can set this flag to true and later verify that it’s true before dismissing the VC:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> updateUIViewController(<span class="hl-kw">_</span> uiViewController: <span class="hl-type">UIViewController</span>, context: <span class="hl-type">Context</span>) {
    <span class="hl-kw">if</span> isPresented {
        <span class="hl-kw">if</span> uiViewController.<span class="hl-prop">presentedViewController</span> == <span class="hl-kw">nil</span> {
            <span class="hl-cmt">// ...</span>
            context.<span class="hl-prop">coordinator</span>.<span class="hl-prop">didPresent</span> = <span class="hl-kw">true</span>
        }
    } <span class="hl-kw">else</span> {
        <span class="hl-kw">if</span> context.<span class="hl-prop">coordinator</span>.<span class="hl-prop">didPresent</span>,
           <span class="hl-kw">let</span> presentedViewController = uiViewController.<span class="hl-prop">presentedViewController</span>,
        <span class="hl-cmt">// ...</span>
    }
}
</code></pre>
<p>Lastly, we can declare an extension on <code>View</code> called <code>presentViewController</code> which takes the same arguments as the modifier type and applies it to <code>self</code>. </p>
<h2 id="the-approach"><a href="#the-approach" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Approach</h2>
<p>Now that we have the ability to present a custom view controller, let’s think about what that VC actually needs to contain for the matched geometry effect. The transition we want to achieve is split into two parts: the matched geometry and everything else. The frames of the views being matched should animate smoothly from source to destination, and the views themselves should be visible the entire time. The rest of the presented content, however, is not matched and should appear with some other animation. There are multiple options, but I’m going to go with a simple fade-in.</p>
<p>So, when put all together, there will be three layers which are (back to front): the the source view, the non-matched part of the presented view, and finally the matched view(s).</p>
<style>
#layer-diagram {
	height: 550px;
	display: flex;
	flex-direction: row;
	align-items: center;
	justify-content: center;
}
#layer-diagram p {
	width: 100%;
	margin: 10px 0;
	text-align: center;
	font-family: -apple-system, system-ui, BlinkMacSystemFont, sans-serif;
}
#layer-container {
	transform: translate(30px, 30px) perspective(1000px) rotateX(-20deg) rotateY(30deg);
	position: relative;
	height: 550px;
	width: 350px;
}
#layer-container > div {
	width: 200px;
	height: 400px;
	position: absolute;
	border: 1px dashed var(--ui-text-color);
}
#layer-container > #red {
	background-color: rgba(255, 0, 0, 0.4);
	top: 0;
	left: 0;
}
#layer-container > #green {
	top: 50px;
	left: 50px;
}
#layer-container > #green > #background {
	background-color: #50a14f;
	width: 100%;
	height: 100%;
	animation: content-layer 5s infinite;
}
#layer-container > #green > p {
	position: absolute;
	top: 0;
	animation: content-layer 5s infinite;
}
#layer-container > #blue {
	top: 100px;
	left: 100px;
}
#layer-container > #blue > #matched {
	position: absolute;
	background-color: blue;
	animation: matched 5s infinite;
}
@keyframes content-layer {
	0%, 80%, 100% { opacity: 0; }
	30%, 50% { opacity: 1; }
}
@keyframes matched {
	0%, 80%, 100% { width: 50px; height: 50px; top: calc(50% - 25px); left: calc(50% - 25px); }
	30%, 50% { width: 100%; height: 100px; top: 50px; left: 0; }
}
</style>
<div id="layer-diagram" class="article-content-wide" aria-labelled="Diagram of the animation's layer structure. The presenter layer at the back is always visible. The presented layer in the middle fades in and out. The matched layer at the front changes shape between a small square while the presented layer is hidden and a larger rectangle when the presented layer is visible.">
	<div id="layer-container">
		<div id="red"><p>Presenter</p></div>
		<div id="green"><div id="background"></div><p>Presented</p></div>
		<div id="blue"><div id="matched"></div></div>
	</div>
</div>
<p>The presented and matched layers will each be their own <code>UIHostingController</code>, containing the respective parts of the SwiftUI view tree that we want to display. They’ll be grouped into a single container view controller, which is the VC that will actually be presented.</p>
<aside class="inline">
<p>It’s not clear at the moment why we need two separate hosting controllers, rather than having everything in the same SwiftUI view and host. There is a reason for this that will be made clear when we to get the actual custom presentation animation. It has to do with the details of how the view controller transition is implemented, so just bear with me for now.</p>
</aside>
<h2 id="collecting-matched-geometry-sources"><a href="#collecting-matched-geometry-sources" class="header-anchor" aria-hidden="true" role="presentation">##</a> Collecting Matched Geometry Sources</h2>
<p>The first step in building the actual effect we’re after is collecting all of the views we want to use as sources as well as their geometries. The views themselves are necessary in addition to the frames because, unlike SwiftUI<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>, we’re displaying the matched views outside of their original position in the view tree.</p>
<p>To send this information up through the view tree, we’ll use a custom preference. The value of the preference will be a dictionary which maps the matched geometry’s ID to a tuple of an <code>AnyView</code> and a <code>CGRect</code>. The view is the type-erased view that’s being matched, and the rect is the frame of the source view. The important part of the preference key is the reducer which, rather than simply overwriting the current value, merges it with the new one. This means that, if there are multiple matched geometry sources in the view tree, reading the preference from higher up in the tree will give us access to <em>all</em> of the sources.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometrySourcesKey: <span class="hl-type">PreferenceKey</span> {
	<span class="hl-kw">static let</span> defaultValue: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)] = [:]
	<span class="hl-kw">static func</span> reduce(value: <span class="hl-kw">inout</span> <span class="hl-type">Value</span>, nextValue: () -&gt; <span class="hl-type">Value</span>) {
		value.<span class="hl-fn">merge</span>(<span class="hl-fn">nextValue</span>(), uniquingKeysWith: { <span class="hl-kw">_</span>, new <span class="hl-kw">in</span> new })
	}
}
</code></pre>
<p>The modifier then uses a <code>GeometryReader</code> to get the frame. It resolves the frame in the global coordinate space, which is what we want, since we’ll present a view controller that fills the window. The geometry reader is placed in a background modifier so that it doesn’t affect the layout of the wrapped view.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometrySourceModifier&lt;Matched: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
	<span class="hl-kw">let</span> id: <span class="hl-type">AnyHashable</span>
    <span class="hl-kw">let</span> matched: <span class="hl-type">Matched</span>

	<span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		content
			.<span class="hl-fn">background</span>(<span class="hl-type">GeometryReader</span> { proxy <span class="hl-kw">in</span>
				<span class="hl-type">Color</span>.<span class="hl-prop">clear</span>
					.<span class="hl-fn">preference</span>(key: <span class="hl-type">MatchedGeometrySourcesKey</span>.<span class="hl-kw">self</span>, value: [
                        id: (<span class="hl-type">AnyView</span>(matched), proxy.<span class="hl-fn">frame</span>(in: .<span class="hl-prop">global</span>))
                    ])
			})
	}
}
</code></pre>
<p>Also of note here is why the <code>matched</code> property is necessary, rather than just using the method’s <code>content</code> parameter. When applying the modifier, SwiftUI doesn’t invoke the <code>body</code> method with the actual wrapped view. Rather, it receives an instance of <code>_ViewModifier_Content</code>, which seems to be a placeholder for the real content. And it can’t be used outside of the modifier (trying to do so will result in nothing rendering), so our source modifier needs to store a copy of the actual wrapped view.</p>
<p>We can then add a little extension to <code>View</code> to make the API nice and SwiftUI-y.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">View</span> {
    <span class="hl-kw">func</span> matchedGeometrySource&lt;ID: <span class="hl-type">Hashable</span>&gt;(id: <span class="hl-type">ID</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-kw">self</span>.<span class="hl-fn">modifier</span>(<span class="hl-type">MatchedGeometrySourceModifier</span>(id: <span class="hl-type">AnyHashable</span>(id), matched: <span class="hl-kw">self</span>))
    }
}
</code></pre>
<p>Next, let’s read the source data that’s collected by the preference and pass it off to the presented view controller. This too will be a modifier, like the SwiftUI presentation ones.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometryPresentationModifier: <span class="hl-type">ViewModifier</span> {
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        content
        	.<span class="hl-fn">backgroundPreferenceValue</span>(<span class="hl-type">MatchedGeometrySourcesKey</span>.<span class="hl-kw">self</span>) { sources <span class="hl-kw">in</span>
                <span class="hl-type">Color</span>.<span class="hl-prop">clear</span>
                    .<span class="hl-fn">presentViewController</span>(<span class="hl-fn">makeVC</span>(sources: sources), isPresented: <span class="hl-prop">$isPresented</span>)
            }
    }
    
    <span class="hl-kw">private func</span> makeVC(sources: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)]) -&gt; () -&gt; <span class="hl-type">UIViewController</span> {
        <span class="hl-kw">return</span> {
            <span class="hl-kw">return</span> <span class="hl-type">MatchedGeometryViewController</span>(sources: sources)
		}
    }
}
</code></pre>
<p>The <code>backgroundPreferenceValue</code> gives us access to the value of the preference and let’s us use it to build part of the view tree. We can’t use the <code>onPreferenceChange</code> modifier since it requires the preference value conform to <code>Equatable</code>, which isn’t possible for the source since it uses <code>AnyView</code>. So instead, we use the background preference modifier which always gives us access to the current value of the preference, without having to check whether it’s changed. But we don’t actually want to show anything in the background, so we attached the VC presentation modifier to the clear color.</p>
<h2 id="the-container-view-controller"><a href="#the-container-view-controller" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Container View Controller</h2>
<p>The container VC is where the bulk of the work is happening, so we’ll start with a simple version that just displays all the sources in the right positions—without any animations or other content—to validate our approach.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryViewController: <span class="hl-type">UIViewController</span> {
    <span class="hl-kw">let</span> sources: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)]
    <span class="hl-kw">var</span> matchedHost: <span class="hl-type">UIHostingController</span>&lt;<span class="hl-type">MatchedContainerView</span>&gt;!
    
    <span class="hl-kw">init</span>(sources: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)]) {
        <span class="hl-kw">self</span>.<span class="hl-prop">sources</span> = sources
        
        <span class="hl-kw">super</span>.<span class="hl-kw">init</span>(nibName: <span class="hl-kw">nil</span>, bundle: <span class="hl-kw">nil</span>)
    }
    
    <span class="hl-kw">required init</span>?(coder: <span class="hl-type">NSCoder</span>) {
        <span class="hl-fn">fatalError</span>(<span class="hl-str">"init(coder:) has not been implemented"</span>)
    }
}
</code></pre>
<p>The container VC will have a hosting controller that’s dedicated to the views that we’re matching between the source and the destination. We’ll set it up in <code>viewDidLoad</code>.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">override func</span> viewDidLoad() {
    <span class="hl-kw">super</span>.<span class="hl-fn">viewDidLoad</span>()
    
    <span class="hl-kw">let</span> sources = <span class="hl-kw">self</span>.<span class="hl-prop">sources</span>.<span class="hl-fn">map</span> { (id: $0.<span class="hl-prop">key</span>, view: $0.<span class="hl-prop">value</span>.<span class="hl-num">0</span>, frame: $0.<span class="hl-prop">value</span>.<span class="hl-num">1</span>) }
    <span class="hl-kw">let</span> matchedContainer = <span class="hl-type">MatchedContainerView</span>(sources: sources, state: state)
    matchedHost = <span class="hl-type">UIHostingController</span>(rootView: matchedContainer)
    matchedHost.<span class="hl-prop">view</span>.<span class="hl-prop">autoresizingMask</span> = [.<span class="hl-prop">flexibleWidth</span>, .<span class="hl-prop">flexibleHeight</span>]
    matchedHost.<span class="hl-prop">view</span>.<span class="hl-prop">frame</span> = view.<span class="hl-prop">bounds</span>
    matchedHost.<span class="hl-prop">view</span>.<span class="hl-prop">backgroundColor</span> = .<span class="hl-prop">clear</span>
    matchedHost.<span class="hl-prop">view</span>.<span class="hl-prop">layer</span>.<span class="hl-prop">zPosition</span> = <span class="hl-num">100</span>
    <span class="hl-fn">addChild</span>(matchedHost)
    view.<span class="hl-fn">addSubview</span>(matchedHost.<span class="hl-prop">view</span>)
    matchedHost.<span class="hl-fn">didMove</span>(toParent: <span class="hl-kw">self</span>)
}
</code></pre>
<p>A couple notes on this:</p>
<ol>
<li>We turn the <code>sources</code> dictionary into an array (and take the opportunity to flatten out the nested tuples) because we’ll need a <code>RandomAccessCollection</code> to use with <code>ForEach</code> to display all the views.</li>
<li>We make the background color clear and raise the z-index so that when we add the content, the matched views appear above it and don’t completely obscure it.</li>
</ol>
<p>The container view for the matched views will, for now, just display all of the views in a <code>ZStack</code> and fix them at their source frames.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedContainerView: <span class="hl-type">View</span> {
    <span class="hl-kw">let</span> sources: [(id: <span class="hl-type">AnyHashable</span>, view: <span class="hl-type">AnyView</span>, frame: <span class="hl-type">CGRect</span>)]
    <span class="hl-kw">@ObservedObject var</span> state: <span class="hl-type">MatchedGeometryState</span>
    
    <span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-type">ZStack</span> {
            <span class="hl-type">ForEach</span>(sources, id: \.<span class="hl-prop">id</span>) { (id, view, frame) <span class="hl-kw">in</span>
                view
                	.<span class="hl-fn">frame</span>(width: frame.<span class="hl-prop">width</span>, height: frame.<span class="hl-prop">height</span>)
                	.<span class="hl-fn">position</span>(x: frame.<span class="hl-prop">midX</span>, y: frame.<span class="hl-prop">midY</span>)
                    .<span class="hl-fn">ignoresSafeArea</span>()
            }
		}
    }
}
</code></pre>
<p>Note that we use the middle x/y coordinates of the source frame, since the <code>position</code> modifier is anchored at the center of the view. We also make the matched view ignore the safe area, since the coordinates we’re using for the position are in the global coordinate space, which extends past the safe area.</p>
<p>Testing this out, we can see that the matched views are indeed displayed at the same position (roughly: the container VC is still actually being presented as a sheet, which is slightly offsetting everything relative to the global coordinate space).</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-type">VStack</span> {
	<span class="hl-type">Image</span>(<span class="hl-str">"pranked"</span>)
		.<span class="hl-fn">resizable</span>()
		.<span class="hl-fn">aspectRatio</span>(contentMode: .<span class="hl-prop">fit</span>)
		.<span class="hl-fn">matchedGeometrySource</span>(id: <span class="hl-str">"image"</span>)
		.<span class="hl-fn">frame</span>(width: <span class="hl-num">100</span>)
	
	<span class="hl-type">Button</span> {
		presented.<span class="hl-fn">toggle</span>()
	} label: {
		<span class="hl-type">Text</span>(<span class="hl-str">"Present"</span>)
	}
}
.<span class="hl-fn">matchedGeometryPresentation</span>(isPresented: <span class="hl-prop">$presented</span>)
</code></pre><figure>
	<div style="display: flex; flex-direction: row; align-items: center;">
		<img src="/2023/swiftui-hero-transition/container-source.png" alt="" style="width: 50%;">
		<img src="/2023/swiftui-hero-transition/container-presented.png" alt="" style="width: 50%;">
	</div>
</figure>
<h2 id="non-matched-content"><a href="#non-matched-content" class="header-anchor" aria-hidden="true" role="presentation">##</a> Non-Matched Content</h2>
<p>Next, let’s build the destination side of the setup and actually display the real content we want to present, not just the matched views. We’ll use a modifier like the source one to collect the geometry of the destination views, with much the same geometry reader technique.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometryDestinationModifier&lt;Matched: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
    <span class="hl-kw">let</span> id: <span class="hl-type">AnyHashable</span>
    <span class="hl-kw">let</span> matched: <span class="hl-type">Matched</span>
    
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        content
        	.<span class="hl-fn">background</span>(<span class="hl-type">GeometryReader</span> { proxy <span class="hl-kw">in</span>
                <span class="hl-type">Color</span>.<span class="hl-prop">clear</span>
                	.<span class="hl-fn">preference</span>(key: <span class="hl-type">MatchedGeometryDestinationFrameKey</span>.<span class="hl-kw">self</span>, value: proxy.<span class="hl-fn">frame</span>(in: .<span class="hl-prop">global</span>))
                    .<span class="hl-fn">onPreferenceChange</span>(<span class="hl-type">MatchedGeometryDestinationFrameKey</span>.<span class="hl-kw">self</span>) { newValue <span class="hl-kw">in</span>
						<span class="hl-cmt">// TODO</span>
                    }
            })
    }
}

<span class="hl-kw">extension</span> <span class="hl-type">View</span> {
    <span class="hl-kw">func</span> matchedGeometryDestination&lt;ID: <span class="hl-type">Hashable</span>&gt;(id: <span class="hl-type">ID</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-kw">self</span>.<span class="hl-fn">modifier</span>(<span class="hl-type">MatchedGeometryDestinationModifier</span>(id: <span class="hl-type">AnyHashable</span>(id), matched: <span class="hl-kw">self</span>))
    }
}
</code></pre>
<p>The difference here is that the preference is only going to contain the frame, so that we can use the <code>onPreferenceChange</code> modifier to listen for changes and update the container VC’s state. Unlike the source, we’re not going to listen for this preference anywhere higher in the view tree. The reason for this is that we need the value of the preference to update state, not to construct another part of the view tree. And <code>onPreferenceChange</code> is the only modifier we have available for that—and it requires the preference’s value be <code>Equatable</code> which it can’t be if we put the <code>AnyView</code> in it.</p>
<p>The preference key itself is very simple: it just holds an optional rect.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometryDestinationFrameKey: <span class="hl-type">PreferenceKey</span> {
    <span class="hl-kw">static let</span> defaultValue: <span class="hl-type">CGRect</span>? = <span class="hl-kw">nil
    static func</span> reduce(value: <span class="hl-kw">inout</span> <span class="hl-type">CGRect</span>?, nextValue: () -&gt; <span class="hl-type">CGRect</span>?) {
        value = <span class="hl-fn">nextValue</span>()
    }
}
</code></pre>
<p>Before we can fill in the todo comment, we need somewhere to put the destination state. We’re going to make a separate <code>ObservableObject</code> which will contain several pieces of state we need in various places for the animation. But for now we just need somewhere to stick the destinations.</p>
<aside>
<p>Why not just use a <code>@State</code> property in the presentation view modifier, and pass a binding to that through to the destination? Because we’ll need to observe values of the destinations property outside of the SwiftUI view tree—and using <code>@Published</code> gives us an easy way to do that.</p>
</aside>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryState: <span class="hl-type">ObservableObject</span> {
    <span class="hl-kw">@Published var</span> destinations: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)] = [:]
}
</code></pre>
<p>Back in the destination view modifier, we’ll get the state object from the environment, and update the destinations dictionary in an on-change modifier:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometryDestinationModifier&lt;Matched: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
    <span class="hl-kw">let</span> id: <span class="hl-type">AnyHashable</span>
    <span class="hl-kw">let</span> matched: <span class="hl-type">Matched</span>
    <span class="hl-kw">@EnvironmentObject private var</span> state: <span class="hl-type">MatchedGeometryState</span>
    
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        content
        	.<span class="hl-fn">background</span>(<span class="hl-type">GeometryReader</span> { proxy <span class="hl-kw">in</span>
                <span class="hl-type">Color</span>.<span class="hl-prop">clear</span>
                	.<span class="hl-fn">preference</span>(key: <span class="hl-type">MatchedGeometryDestinationFrameKey</span>.<span class="hl-kw">self</span>, value: proxy.<span class="hl-fn">frame</span>(in: .<span class="hl-prop">global</span>))
                    .<span class="hl-fn">onPreferenceChange</span>(<span class="hl-type">MatchedGeometryDestinationFrameKey</span>.<span class="hl-kw">self</span>) { newValue <span class="hl-kw">in
						if let</span> newValue {
                            state.<span class="hl-prop">destinations</span>[id] = (<span class="hl-type">AnyView</span>(matched), newValue)
                        }
                    }
            })
    }
}
</code></pre>
<p>Getting the object from the environment is all well and good, but we still need to create it and inject it. For the animation, we’ll want access to some of the state properties in the source modifier, so the object needs to be created somewhere on the source side, rather than being owned by the container view controller. Since the presentation modifier needs to be at a higher level in the view tree than the source modifiers (which means it serves as the “namespace” for the transition, like the <code>Namespace.ID</code> parameter of SwiftUI’s effect), this is a natural place to put it.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometryPresentationModifier: <span class="hl-type">ViewModifier</span> {
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    <span class="hl-kw">@StateObject private var</span> state = <span class="hl-type">MatchedGeometryState</span>()
    
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        content
        	.<span class="hl-fn">environmentObject</span>(state)
        	<span class="hl-cmt">// ...</span>
    }
    
    <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>We also pass it into the VC’s constructor in the <code>makeVC</code> function, and then in the constructore it’s stored in a <code>state</code> property on the VC (code omitted for brevity<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>).</p>
<p>But we’re not done yet. We still need actual content to present, which is where the destination modifier will be used, so we’ll add another property to the presentation modifier and update our View extension function:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">View</span> {
    <span class="hl-kw">func</span> matchedGeometryPresentation&lt;Presented: <span class="hl-type">View</span>&gt;(isPresented: <span class="hl-type">Binding</span>&lt;<span class="hl-type">Bool</span>&gt;, <span class="hl-kw">@ViewBuilder</span> presenting: () -&gt; <span class="hl-type">Presented</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-kw">self</span>.<span class="hl-fn">modifier</span>(<span class="hl-type">MatchedGeometryPresentationModifier</span>(isPresented: isPresented, presented: <span class="hl-fn">presenting</span>()))
    }
}
<span class="hl-kw">struct</span> MatchedGeometryPresentationModifier&lt;Presented: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
    <span class="hl-kw">@Binding var</span> isPresented: <span class="hl-type">Bool</span>
    <span class="hl-kw">let</span> presented: <span class="hl-type">Presented</span>
    <span class="hl-kw">@StateObject private var</span> state = <span class="hl-type">MatchedGeometryState</span>()
    <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The <code>presented</code> view also gets passed into the VC, and we’ll make it generic over the view type—to avoid any more type-erasing then we strictly need. We’ll also add another hosting controller which will display the presented content.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryViewController&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">UIViewController</span> {
    <span class="hl-kw">let</span> sources: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)]
    <span class="hl-kw">let</span> content: <span class="hl-type">Content</span>
    <span class="hl-kw">let</span> state: <span class="hl-type">MatchedGeometryState</span>
    <span class="hl-kw">var</span> contentHost: <span class="hl-type">UIHostingController</span>&lt;<span class="hl-type">ContentContainerView</span>&lt;<span class="hl-type">Content</span>&gt;&gt;!
    <span class="hl-kw">var</span> matchedHost: <span class="hl-type">UIHostingController</span>&lt;<span class="hl-type">MatchedContainerView</span>&gt;!
    <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>And we’ll setup the new hosting controller in <code>viewDidLoad</code> much the same as the other one, but this time providing the <code>content</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryViewController&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">UIViewController</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">override func</span> viewDidLoad() {
        <span class="hl-cmt">// ...</span>
        <span class="hl-kw">let</span> contentContainer = <span class="hl-type">ContentContainerView</span>(content: content, state: state)
        contentHost = <span class="hl-type">UIHostingController</span>(rootView: contentContainer)
        contentHost.<span class="hl-prop">view</span>.<span class="hl-prop">autoresizingMask</span> = [.<span class="hl-prop">flexibleWidth</span>, .<span class="hl-prop">flexibleHeight</span>]
        contentHost.<span class="hl-prop">view</span>.<span class="hl-prop">frame</span> = view.<span class="hl-prop">bounds</span>
        <span class="hl-fn">addChild</span>(contentHost)
        view.<span class="hl-fn">addSubview</span>(contentHost.<span class="hl-prop">view</span>)
        contentHost.<span class="hl-fn">didMove</span>(toParent: <span class="hl-kw">self</span>)
    }
}
</code></pre>
<p>The <code>ContentContainerView</code> is very simple: it just displays the content and provides access to our state object through the environment—finally bringing us full-circle to how the destination modifier will access it.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ContentContainerView&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">View</span> {
    <span class="hl-kw">let</span> content: <span class="hl-type">Content</span>
    <span class="hl-kw">let</span> state: <span class="hl-type">MatchedGeometryState</span>
    
    <span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        content
        	.<span class="hl-fn">environmentObject</span>(state)
    }
}
</code></pre>
<p>Now, if we tweak our test code to add some content to the presentation, we can see that</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-cmt">// ...</span>
.<span class="hl-fn">matchedGeometryPresentation</span>(isPresented: <span class="hl-prop">$presented</span>) {
    <span class="hl-type">VStack</span> {
        <span class="hl-type">Image</span>(<span class="hl-str">"pranked"</span>)
        	.<span class="hl-fn">resizable</span>()
        	.<span class="hl-fn">aspectRatio</span>(contentMode: .<span class="hl-prop">fit</span>)
        	.<span class="hl-fn">matchedGeometryDestination</span>(id: <span class="hl-str">"image"</span>)
        
        <span class="hl-type">Text</span>(<span class="hl-str">"Hello!"</span>)
	}
}
</code></pre><figure>
	<div style="display: flex; flex-direction: row; align-items: center;">
		<img src="/2023/swiftui-hero-transition/content-presented.png" alt="" style="width: 50%;">
	</div>
</figure>
<p>But it doesn’t look quite right yet. We are displaying all the layers, but we’re <em>always</em> displaying all the layers—which is why we’ve got two copies of the image visible. To fix that, we’ll need to build:</p>
<h2 id="the-animation"><a href="#the-animation" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Animation</h2>
<p>With everything in place, let’s actually put the pieces together and build the animation. First off, we’ll set the modal presentation style and transitioning delegate so we can completely control the presentation animation.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryViewController&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">UIViewController</span>, <span class="hl-type">UIViewControllerTransitioningDelegate</span> {
    <span class="hl-kw">init</span>(sources: [<span class="hl-type">AnyHashable</span>: (<span class="hl-type">AnyView</span>, <span class="hl-type">CGRect</span>)], content: <span class="hl-type">Content</span>, state: <span class="hl-type">MatchedGeometryState</span>) {
        <span class="hl-cmt">// ...</span>
        modalPresentationStyle = .<span class="hl-prop">custom</span>
        transitioningDelegate = <span class="hl-kw">self</span>
    }
}
</code></pre>
<p>We’ll also make the VC conform to the transitioning delegate protocol. Ordinarily, I’d declare this conformance in an extension, but Swift classes with generic types cannot declare <code>@objc</code> protocol conformances in extensions, only on the class itself.</p>
<p>The methods we’ll implement for this protocol are all simple: the actual work is offloaded to other classes. For the presenting animation controller, we’ll just instantiate another class.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> animationController(forPresented presented: <span class="hl-type">UIViewController</span>, presenting: <span class="hl-type">UIViewController</span>, source: <span class="hl-type">UIViewController</span>) -&gt; <span class="hl-type">UIViewControllerAnimatedTransitioning</span>? {
	<span class="hl-kw">return</span> <span class="hl-type">MatchedGeometryPresentationAnimationController</span>&lt;<span class="hl-type">Content</span>&gt;()
}
</code></pre>
<p>Note that the animation controller class is generic over the same content view type as the container VC. This is so that, in the implementation of the animation, we can cast the <code>UIViewController</code> we’re given to the concrete type of the VC that we know is being presented.</p>
<p>In the animation controller class, the <code>transitionDuration</code> method will just return 1 second. We’re actually going to use a spring animation, so this duration isn’t quite accurate, but we need something to go here, and this is the duration we’ll use when configuring the animator, even if the timing is ultimately driven by the spring.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryPresentationAnimationController&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">NSObject</span>, <span class="hl-type">UIViewControllerAnimatedTransitioning</span> {
    <span class="hl-kw">func</span> transitionDuration(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>?) -&gt; <span class="hl-type">TimeInterval</span> {
        <span class="hl-kw">return</span> <span class="hl-num">1</span>
    }
    <span class="hl-kw">func</span> animateTransition(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>) {
        <span class="hl-cmt">// TODO</span>
    }
}
</code></pre>
<p>All the actual work will happen inside the <code>animateTransition</code> method. The first thing we need to do is pull out our view controller (using the <code>.to</code> key, because it’s the destination of the presentation animation) and add it to the transition container view.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> animateTransition(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>) {
    <span class="hl-kw">let</span> matchedGeomVC = transitionContext.<span class="hl-fn">viewController</span>(forKey: .<span class="hl-prop">to</span>) <span class="hl-kw">as</span>! <span class="hl-type">MatchedGeometryViewController</span>&lt;<span class="hl-type">Content</span>&gt;
    <span class="hl-kw">let</span> container = transitionContext.<span class="hl-prop">containerView</span>
    
    container.<span class="hl-fn">addSubview</span>(matchedGeomVC.<span class="hl-prop">view</span>)
}
</code></pre>
<p>Next, let’s get the fade-in working for the non-matched content. We start of by setting that layer’s opacity to 0, making it completely transparent. Then we set up a <code>UIViewPropertyAnimator</code> with the same spring configuration we’re going to ultimately use for the matched geometry effect. That animator will set the layer’s opacity to 1, bringing it fully opaque. And when the animation completes, we need to inform the transition context.</p>
<aside class="inline">
<p>And here is the answer from before, the reason why we have two separate hosting controllers: SwiftUI animations don’t have completion handlers, but we need one in order to call the VC transition context’s completion method. With a simple, time-based animation we could just enqueue something on the main runloop after the appropriate delay (though this would have the unfortunate side-effect of breaking the simulator’s Slow Animations mode, which was a very useful tool when debugging this). But, I want to use a spring animation, since it feels much nicer, and calculating the appropriate amount of time is trickier.</p>
<p>The other piece of the puzzle is that <code>UIViewPropertyAnimator</code> will call the completion handler immediately if no animations are added. So, we do need to actually animate something. And we can’t animate anything inside SwiftUI, since it uses it’s own independent animation system. Thus, we split the content into two hosting controllers—one of which is animated by SwiftUI and the other by UIKit. So long as we take care to match the same spring configuration, they’ll look perfectly good together.</p>
</aside>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> animateTransition(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>) {
    <span class="hl-cmt">// ...</span>
    
    matchedGeomVC.<span class="hl-prop">contentHost</span>.<span class="hl-prop">view</span>.<span class="hl-prop">layer</span>.<span class="hl-prop">opacity</span> = <span class="hl-num">0</span>
    <span class="hl-kw">let</span> spring = <span class="hl-type">UISpringTimingParameters</span>(mass: <span class="hl-num">1</span>, stiffness: <span class="hl-num">150</span>, damping: <span class="hl-num">15</span>, initialVelocity: .<span class="hl-prop">zero</span>)
    <span class="hl-kw">let</span> animator = <span class="hl-type">UIViewPropertyAnimator</span>(duration: <span class="hl-kw">self</span>.<span class="hl-fn">transitionDuration</span>(using: transitionContext), timingParameters: spring)
    animator.<span class="hl-fn">addAnimations</span> {
        matchedGeomVC.<span class="hl-prop">contentHost</span>.<span class="hl-prop">view</span>.<span class="hl-prop">layer</span>.<span class="hl-prop">opacity</span> = <span class="hl-num">1</span>
    }
    animator.<span class="hl-fn">addCompletion</span> { <span class="hl-kw">_ in</span>
        transitionContext.<span class="hl-fn">completeTransition</span>(<span class="hl-kw">true</span>)
    }
}
</code></pre>
<p>We can ignore the position parameter the animation completion handler receives, because we’re never going to stop or cancel the animation.</p>
<p>Testing the animation now, you can see that the non-matched content does indeed fade in. But there’s still an issue, even aside from the fact that we’re not handling the matched geometry effect yet. Specifically, the source and destination views are visible during the animation, which breaks the illusion that a single view is moving seamless from one place to another.</p>
<p>To take care of this, we’ll add another property to the state object. It will store whether the animation is currently taken place.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryState: <span class="hl-type">ObservableObject</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">@Published var</span> animating: <span class="hl-type">Bool</span> = <span class="hl-kw">false</span>
}
</code></pre>
<p>The source and destination modifiers can then use this to make their respective wrapped views entirely transparent during the animation.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedGeometrySourceModifier&lt;Matched: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">@EnvironmentObject private var</span> state: <span class="hl-type">MatchedGeometryState</span>
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-cmt">// ...</span>
        	.<span class="hl-fn">opacity</span>(state.<span class="hl-prop">animating</span> ? <span class="hl-num">0</span> : <span class="hl-num">1</span>)
    }
}
<span class="hl-kw">struct</span> MatchedGeometryDestinationModifier&lt;Matched: <span class="hl-type">View</span>&gt;: <span class="hl-type">ViewModifier</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">func</span> body(content: <span class="hl-type">Content</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-cmt">// ...</span>
        	.<span class="hl-fn">opacity</span>(state.<span class="hl-prop">animating</span> ? <span class="hl-num">0</span> : <span class="hl-num">1</span>)
    }
}
</code></pre>
<p>Then the animation controller can set the <code>animating</code> state to <code>true</code> when the animation starts and back to <code>false</code> when it ends.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> animateTransition(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>) {
    <span class="hl-cmt">// ...</span>
    matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">animating</span> = <span class="hl-kw">false</span>
    <span class="hl-cmt">// ...</span>
    animator.<span class="hl-fn">addCompletion</span> { <span class="hl-kw">_ in</span>
        transitionContext.<span class="hl-fn">completeTransition</span>(<span class="hl-kw">true</span>)
        matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">animating</span> = <span class="hl-kw">false</span>
    }
    animator.<span class="hl-fn">startAnimating</span>()
}
</code></pre>
<p>Alright, now we can move on to the meat and potatoes of actually matching the views.</p>
<p>The first step is adding another property to the state object. <code>currentFrames</code> will store, well, the <em>current</em> frames of each matched view. Initially, it will contain the source frames, and then setting it to the destination frames will trigger the SwiftUI side of the animation.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryState: <span class="hl-type">ObservableObject</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">@Published var</span> currentFrames: [<span class="hl-type">AnyHashable</span>: <span class="hl-type">CGRect</span>] = [:]
    <span class="hl-kw">@Published var</span> mode: <span class="hl-type">Mode</span> = .<span class="hl-prop">presenting</span>
    
    <span class="hl-kw">enum</span> Mode {
        <span class="hl-kw">case</span> presenting, dismissing
    }
}
</code></pre>
<p>Then we can update the matched container view to use the current frame, rather than always using the source one. The other change we’re going to make to the matched views is to blend between the source and destination matched views.</p>
<p>This handles the possibility that there are visual differences between the two, beyond their sizes and positions. We’ll perform this blending by fading in the matched view from the destination over top of the matched view from the source<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>. By keeping them at the same position, we can ensure it still appears to be a single view that’s changing.</p>
<p>We also need a property for the mode—whether we’re presenting or dismissing—since this fade depends on the direction of the animation. When presenting, the destination view’s opacity should go from 0→1, going from transparent to fully opaque, but when dismissing it should go the other way around.</p>
<p>In the <code>MatchedContainerView</code>, we’ll split creating the actual matched view into a separate function, since there’s a fair bit of logic that goes into it:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedContainerView: <span class="hl-type">View</span> {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
        <span class="hl-type">ZStack</span> {
            <span class="hl-type">ForEach</span>(sources, id: \.<span class="hl-prop">id</span>) { (id, view, <span class="hl-kw">_</span>) <span class="hl-kw">in</span>
            	<span class="hl-fn">matchedView</span>(id: id, source: view)
            }
        }
    }
}
</code></pre>
<p>The <code>matchedView</code> function will acually generate this view</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> MatchedContainerView: <span class="hl-type">View</span> {
	<span class="hl-cmt">// ...</span>
    <span class="hl-kw">func</span> matchedView(id: <span class="hl-type">AnyHashable</span>, source: <span class="hl-type">AnyView</span>) -&gt; <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		<span class="hl-kw">let</span> frame = state.<span class="hl-prop">currentFrames</span>[id]!
        <span class="hl-kw">let</span> dest = state.<span class="hl-prop">destinations</span>[id]!.<span class="hl-num">0</span>
        <span class="hl-kw">let</span> destOpacity: <span class="hl-type">Double</span>
        <span class="hl-kw">if case</span> .<span class="hl-prop">presenting</span> = state.<span class="hl-prop">mode</span> {
            destOpacity = state.<span class="hl-prop">animating</span> ? <span class="hl-num">1</span> : <span class="hl-num">0</span>
        } <span class="hl-kw">else</span> {
            destOpacity = state.<span class="hl-prop">animating</span> ? <span class="hl-num">0</span> : <span class="hl-num">1</span>
        }
        <span class="hl-kw">return</span> <span class="hl-type">ZStack</span> {
            source
            dest
            	.<span class="hl-fn">opacity</span>(destOpacity)
        }
        .<span class="hl-fn">frame</span>(width: frame.<span class="hl-prop">width</span>, height: frame.<span class="hl-prop">height</span>)
	    .<span class="hl-fn">position</span>(x: frame.<span class="hl-prop">midX</span>, y: frame.<span class="hl-prop">midY</span>)
        .<span class="hl-fn">ignoresSafeArea</span>()
        .<span class="hl-fn">animation</span>(.<span class="hl-fn">interpolatingSpring</span>(mass: <span class="hl-num">1</span>, stiffness: <span class="hl-num">150</span>, damping: <span class="hl-num">15</span>, initialVelocity: <span class="hl-num">0</span>), value: frame)
    }
}
</code></pre>
<p>This function gets the current frame for the view from the state object based on its ID. It also looks up the corresponding destination view. They’re layered together in a <code>ZStack</code> with an opacity modifier applied to the destination, so that it fades in on top of the source.</p>
<p>Note that we only apply the opacity to the destination view. If the opacity animation also applied to the source view (albeit in reverse), the animation would pass through intermediate states where both views are partially transparent, resulting in the background content being visible through the matched view, which can look strange.</p>
<p>The frame and position modifiers also move to the stack, so that they apply to both the source and destination views. We also add an animation modifier to the stack, using a spring animation and making sure to match the configuration to the <code>UISpringTimingParameters</code> we used for the view controller animation.</p>
<p>Next, we need to return to the UIKit animation controller, since we have yet to actually kick off the matched geometry animation.</p>
<p>This was the trickiest part to figure out when I was building this, since we need to wait for the destination SwiftUI view tree to update and layout in order actually know where all the destinations are. If we were to just kick off the animation immediately, there would be no destination views/frames, and the force unwraps above would fail. What’s more, providing a default for the animation and swapping out the real destination frame while the animation is in-flight ends up looking rather janky, since the spring animation has a fair bit of momentum. So, the destination needs to fully laid out before we can even start the SwiftUI side of the animation.</p>
<p>Since SwiftUI doesn’t seem to provide any way of forcing the view tree to update and fully layout immediately, we have to set up some listener that will fire once the destination is ready. Since we already have an <code>ObservableObject</code> containing the destination info in a <code>@Published</code> property, we can subscribe to that with Combine.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> animationTransition(using transitionContext: <span class="hl-type">UIViewControllerContextTransitioning</span>) {
    <span class="hl-cmt">// ...</span>
    container.<span class="hl-fn">addSubview</span>(matchedGeomVC.<span class="hl-prop">view</span>)
    
    <span class="hl-kw">let</span> cancellable = matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">$destinations</span>
    	.<span class="hl-fn">filter</span> { destinations <span class="hl-kw">in</span>
            matchedGeomVC.<span class="hl-prop">sources</span>.<span class="hl-fn">allSatisfy</span> { source <span class="hl-kw">in</span>
            	destinations.<span class="hl-prop">keys</span>.<span class="hl-fn">contains</span>(source.<span class="hl-prop">key</span>)
            }
		}
    	.<span class="hl-fn">first</span>()
    	.<span class="hl-fn">sink</span> { <span class="hl-kw">_ in</span>
            
        }
    
    matchedGeomVC.<span class="hl-prop">view</span>.<span class="hl-prop">layer</span>.<span class="hl-prop">opacity</span> = <span class="hl-num">0</span>
    <span class="hl-cmt">// ...</span>
    animator.<span class="hl-fn">addCompletion</span> { <span class="hl-kw">_ in</span>
		<span class="hl-cmt">// ...</span>
		cancellable.<span class="hl-fn">cancel</span>()
		matchedGeomVC.<span class="hl-prop">matchedHost</span>?.<span class="hl-prop">view</span>.<span class="hl-prop">isHidden</span> = <span class="hl-kw">true</span>
	}
    animator.<span class="hl-fn">startAnimation</span>()
}
</code></pre>
<p>We use the <code>.filter</code> to wait until the state’s destinations dictionary contains entries for all of the sources. We also use the <code>.first()</code> operator, since we only want the sink closure to fire once—triggering the animation multiple times would mess up the animation and the state tracking we’re doing.</p>
<p>Subscribing to the publisher creates an <code>AnyCancellable</code> which we cancel in the animator’s completion handler—both because, if the destinations never became available, we don’t want to keep listening, and because we need to keep the cancellable alive for the duration of the animation (otherwise it would be deinitialized when the <code>animateTransition</code> method returns, cancelling our subscription).</p>
<p>When the publisher fires and all the state is present, we need to do a few things:</p>
<ol>
<li>Add the matched hosting controller (as noted before, we can no longer do this immediately, since all the requisite state for the <code>MatchedContainerView</code> isn’t available initially).</li>
<li>Prepare the initial state of all the properties that are involved in the animation.</li>
<li>Set the new values to start the animation.</li>
</ol>
<p>This takes care of the first two points, and is fairly straightforward:</p>
<pre class="highlight" data-lang="swift"><code>.<span class="hl-fn">sink</span> { <span class="hl-kw">_ in</span>
    matchedGeomVC.<span class="hl-fn">addMatchedHostingController</span>()

    matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">mode</span> = .<span class="hl-prop">presenting</span>
    matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">currentFrames</span> = matchedGeomVC.<span class="hl-prop">sources</span>.<span class="hl-fn">mapValues</span>(\.<span class="hl-num">1</span>)
}
</code></pre>
<p>Just make sure to factor the code for adding the matched hosting controller out of <code>viewDidLoad</code> and into a separate method.</p>
<p>The third point is slightly trickier. We can’t just immediately set the new values, even in a <code>withAnimation</code> closure. Setting the new values needs to take place in a new transaction, once the view has already been updated with the intiial values. So, we wait one runloop iteration and then set the new values to kick off the animation.</p>
<pre class="highlight" data-lang="swift"><code>.<span class="hl-fn">sink</span> { <span class="hl-kw">_ in</span>
	<span class="hl-cmt">// ...</span>
    <span class="hl-type">DispatchQueue</span>.<span class="hl-prop">main</span>.<span class="hl-fn">async</span> {
        matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">animating</span> = <span class="hl-kw">true</span>
        matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">currentFrames</span> = matchedGeomVC.<span class="hl-prop">state</span>.<span class="hl-prop">destinations</span>.<span class="hl-fn">mapValues</span>(\.<span class="hl-num">1</span>)
	}
}
</code></pre>
<p>And with that, the presentation animation is finally complete! We can present a SwiftUI view fullscreen, and have certain parts of the source view smoothly animate to their positions in the destination view, with everything else fading in in between the source and destination views.</p>
<figure>
	<video controls style="max-width: 50%; margin: 0 auto; display: block;" title="">
		<source src="/2023/swiftui-hero-transition/hero.mp4" type="video/mp4">
	</video>
</figure>
<p>(The hitch towards the end is an artifact of the simulator screen recording, it’s not actually present in the simulator or on-device.)</p>
<h2 id="dismiss-animation"><a href="#dismiss-animation" class="header-anchor" aria-hidden="true" role="presentation">##</a> Dismiss Animation</h2>
<p>The dismiss animation is implemented in a very similar manner, so I won’t go over it in detail. </p>
<p>There’s another animation controller, which is returned from <code>animationController(forDismissed:)</code> which does pretty much the same thing as the presentation animation but in reverse. It sets the state to <code>.dismissing</code>, and the current frames to the destination frames. Then, one runloop iteration later, it sets <code>animating = true</code> and switches the current frames to the source frames.</p>
<p>Unlike the presentation animation, the dismiss one doesn’t need the workaround with the <code>$destinations</code> publisher, since when the dismiss is happening, we know that the presented view must already be all setup and laid-out.</p>
<p>The only slight wrinkle the dismiss animation needs is that, in order to work with the <code>UIViewController</code> presentation abstraction we built, there also needs to be a presentation controller that notifies its delegate when the dismiss transition begins:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> MatchedGeometryViewController&lt;Content: <span class="hl-type">View</span>&gt;: <span class="hl-type">UIViewController</span>, <span class="hl-type">UIViewControllerTransitioningDelegate</span> {
	<span class="hl-cmt">// ...</span>
    <span class="hl-kw">func</span> presentationController(forPresented presented: <span class="hl-type">UIViewController</span>, presenting: <span class="hl-type">UIViewController</span>?, source: <span class="hl-type">UIViewController</span>) -&gt; <span class="hl-type">UIPresentationController</span>? {
        <span class="hl-kw">return</span> <span class="hl-type">MatchedGeometryPresentationController</span>(presentedViewController: presented, presenting: presenting)
    }
}

<span class="hl-kw">class</span> MatchedGeometryPresentationController: <span class="hl-type">UIPresentationController</span> {
    <span class="hl-kw">override func</span> dismissalTransitionWillBegin() {
        <span class="hl-kw">super</span>.<span class="hl-fn">dismissalTransitionWillBegin</span>()
        delegate?.<span class="hl-prop">presentationControllerWillDismiss</span>?(<span class="hl-kw">self</span>)
    }
}
</code></pre><h2 id="future-work"><a href="#future-work" class="header-anchor" aria-hidden="true" role="presentation">##</a> Future Work</h2>
<p>There are a few loose ends I have deliberately left as an exercise for you, dear reader.<sup>(definitely not because I didn’t feel like spending the time to work through them)</sup></p>
<ol>
<li>Recreating the <code>properties</code> and <code>anchor</code> parameters of <code>matchedGeometryEffect</code></li>
</ol>
<p>I chose not to implement these because I don’t have any need for them, but given how we’re displaying and positioning the matched views, it should be pretty clear how you could go about adding similar functionality.</p>
<ol start="2">
<li>Handling device rotation</li>
</ol>
<p>If the user’s device is rotated while the matched VC is presented, the source frames will become invalid. There are a couple ways you could approach  this. One is figuring out why the source frames aren’t updated while the VC is presented. Or, if that doesn’t have an easy fix, detecting when the window size changes while the VC is presented and then using a different animation for the dismissal.</p>
<ol start="3">
<li>Conditional matched views</li>
</ol>
<p>Because of the  way we’ve implemented the <code>$destinations</code> publisher workaround, if there are source views that never end up getting a destination view, the entire matched part of the animation will just never run. This is rather less than ideal, particularly if there are views that you want to match that may not always be present in the destination.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>Overall, I’m very happy with how this implementaiton turned out. I won’t claim it’s straightforward, but I think it’s relatively un-hacky for what it’s doing and has been very reliable in my testing. And, if you’ve got the latest Tusker release, you’re already running this code.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>The SwiftUI Lab has a good <a href="https://github.com/swiftui-lab/swiftui-hero-animations" data-link="github.com/swiftui-lab/swiftui-hero-anim…">example</a> of this technique. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>If you want to convince yourself that SwiftUI works by moving the matched views in-place, try playing around with the other of the <code>clipped</code> and <code>matchedGeometryEffect</code> modifiers on the same view. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>“Brevity,” he says, at over 3000 words and counting. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>Again, if you want to convince yourself that this is what SwiftUI’s doing, try it out with the regular <code>matchedGeometryEffect</code>. Make the removed view, for example, <code>Color.red</code> and the inserted one <code>Color.blue</code> and see that the colors fades between the two during the animation. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Portable Identity for ActivityPub</title>
      <link>https://shadowfacts.net/2023/activitypub-portable-identity/</link>
      <category>activitypub</category>
      <guid>https://shadowfacts.net/2023/activitypub-portable-identity/</guid>
      <pubDate>Mon, 01 May 2023 17:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Bluesky has been making waves recently, both on its own merits and how it contrasts implementation-wise with ActivityPub/the fedivese. There are a bunch of ways it differs from other implementations of decentralized social media, but there’s one in particular I want to focus on: portable identity. The idea that I should be able to take all of my data, everything that my identity consists of, and move wholesale to a new instance/server/PDS/whatever you call it. Bluesky permits this<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. Mastodon and most (all?) other ActivityPub implementations do not. But that doesn’t have to be the case, nothing about ActivityPub—architecturally speaking—is incompatible with this vision.</p>
<!-- excerpt-end -->
<p>One of the more common feature requests for Mastodon (third only, I’d guess, to quote posts and search) is the ability to migrate accounts <em>with their posts</em>. Without this ability, people get trapped on the first instance they try<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>. Maybe they’d prefer to be on a different instance, closer to a particular community, or somewhere with different moderation standards. But all their posts are stuck right where they are, and it ends up taking a huge, calamitous event—like an instance shutting down—to overcome that inertia and actually move.</p>
<p>So what’s standing in the way, and why aren’t posts portable already? Well, here’s an (abriged) example of the ActivityPub representation of a Mastodon post:</p>
<pre class="highlight" data-lang="json"><code>{
  <span class="hl-key">&quot;@context&quot;</span>: [<span class="hl-str">&quot;https://www.w3.org/ns/activitystreams&quot;</span>],
  <span class="hl-key">&quot;id&quot;</span>: <span class="hl-str">&quot;https://mastodon.social/users/shadowfacts/statuses/15287&quot;</span>,
  <span class="hl-key">&quot;type&quot;</span>: <span class="hl-str">&quot;Note&quot;</span>,
  <span class="hl-key">&quot;content&quot;</span>: <span class="hl-str">&quot;&lt;p&gt;Hello, world!&lt;/p&gt;&quot;</span>
}
</code></pre>
<p>Notice anything about it? The ID of post tells you where it is. However it doesn’t just identify where the document can be found; it also identifies where it’s <em>hosted</em>. This property is true of all object identifiers in Mastodon (and just about every other serivce that implements ActivityPub).</p>
<aside>
<p>An interesting question is why is this the case? Personally, I think the most likely answer is that folks building AP backends have prior web development experience. And outside of decentralized systems, the simplest way of identifying an object from it’s URL is using a path parameter. So you get paths that look like <code>/users/:username/statuses/:status_id</code>. And then when you need something to use as an AP identifier, you use the whole URL. So dereferencing it ends up being trivial: your server just looks up the object in its database, same as ever. But that’s exporting an implementation detail: your primary key (the attribute by which a post is identified in <em>your</em> database) means nothing to me. It unnecessarily ties the post to the server where it originated.</p>
</aside>
<p>It’s as if the cannonical URL of this post were <code>https://5.161.136.163/2023/activitypub-portable-identity/</code>. All of a sudden, this post isn’t controlled by me, the person who holds the domain <code>shadowfacts.net</code>, but instead it’s controlled by my server host, who controls that IP address. I couldn’t change hosting providers without losing all of my content and my social graph<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup> (in this example, the readers of my blog). Sound familiar?</p>
<p>It doesn’t have to be like this. My posts don’t belong to the service I’m using to host them. They belong to <em>me</em>. The identity of the posts I published should be tied to me and not to the service I’m using to host them. That is, the IDs need to be resolvable through something (hint: a domain) that <em>I</em> control before ultimately reaching my host.</p>
<p>Nothing about the ActivityPub spec, as I read it, prohibits this. §3.1 says that object identifiers need to be publicly dereferencable. It notes that their authority (i.e., domain and port) has to belong to the originating server—but there’s nothing to suggest that “server” is distinct from a domain, or that the server-to-host mapping can’t be many-to-one.</p>
<p>To make this vision of portable identities to work, there needs to be an additional layer that performs the mapping of identities to hosts.</p>
<p>For people who own domains, this is easy: they just point the DNS records at their host and tell their host that their identity should be served from their domain. Migrating your identity to another host is then a matter of updating DNS records.</p>
<p>But this shouldn’t be accessible only to those who have the resources and technical know-how to administer a domain<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>. There needs to be some public identity resolver that anyone can use. The simplest way to implement this, I think, is as a service (or services, there’s no reason this has to be a centralizing force) that lets anyone point a subdomain at their host. The identity resolver needs to have a way of mapping any URL back to its host and I think this is easiest (avoiding the need to agree on URL formats) and least resource-intensive method (because you’re not hosting actual content). Having a separate service that’s built on top of DNS lets you abstract away the techinical details from people who don’t care, and would allow a more straightforward signup experience by providing an API to let people create an identity while signing up for an instance, rather than forcing it to be a separate process.</p>
<p>The hard part however, is the social one: we collectively need to agree that the identity resolution layer is <em>infrastructure</em> and not somewhere moderation actions should take place. To use the web analogy again: people publishing objectionable content get kicked off web hosts, but it’s far rarer that their domain name is taken away. The same has to be true of any shared identity provider/resolver, otherwise we end up in the exact same situation we’re in now.</p>
<p>Adding to the social problem is the risk that vanity URLs become popular, and then the desire shifts to migrating between identity resolvers. Solving this problem is possible, although it ends up requiring a larger architectural change to existing ActivityPub implementations. One possible approach, using DIDs, is discussed below. Another approach is splitting the identity resolver from the apparent username. A username that’s written as <code>@someone@example.com</code> could be looked up by using WebFinger on <code>example.com</code> which would then respond with the actual AP ID of the user as <code>https://someone.net</code> which would in turn be hosted by <code>example.com</code> (i.e., DNS records for <code>someone.net</code> point to <code>example.com</code>). This breaks a number of assumptions Mastodon and clients make about the mention format but it is architecturally possible. I do think this is a problem worth solving, but in the interest of supporting account portability in terms of what people usually care about—moderation decisions, servers going down—it isn’t a requirement.</p>
<p>The key advantage of this approach is that resolving a portable identity or object ID into a concrete ActivityPub object is no different than it is now. You just make an HTTP request, and get back an AP object. Where it’s actually hosted, and therefore changing the host, does not matter to you. All your references to the migrated people and posts do not change.</p>
<h2 id="ux"><a href="#ux" class="header-anchor" aria-hidden="true" role="presentation">##</a> UX</h2>
<p>So under this vision, what does the complete migration process actually look like?</p>
<ol>
<li>You export an archive of your account from your current instance.</li>
<li>You import that archive into your new account.</li>
<li>You point your identity at the new server (either by changing your DNS records, or by updating your information in the resolver’s directory described before).</li>
</ol>
<p>In some ways, that’s actually simpler than the current migration process. You don’t have to remember to make your new account point back at your old account before initiating the migration. You don’t have to deal with the distinction between data that’s part of the direct server-to-server migration (your followers) and what’s not (who you follow). You don’t have to retry the migration process because some of your followers’ servers missed the notice and didn’t follow your new account. From everyone else’s point of view, the migration just seamlessly happens.</p>
<h2 id="getting-there"><a href="#getting-there" class="header-anchor" aria-hidden="true" role="presentation">##</a> Getting There</h2>
<p>If you look at Mastodon issue <a href="https://github.com/mastodon/mastodon/issues/12423" data-link="github.com/mastodon/mastodon/issues/1242…">#12432</a> “Support Post Migration”, you will find an incredibly long thread spanning three and a half years of discussion. The discussion primarily comes down to the fact that Mastodon’s architectural decisions are not conducive to this approach.</p>
<p>As noted before, all of Mastodon’s AP identifiers are scoped to the host, not to the user’s identity. Transferring posts to another server would mean <em>changing</em> those identifiers and breaking every existing reference to a post—both on the old instance and, more complicatedly, every other instance in the network that’s aware of them. The former is possible, if potentially resource-intensive. But the latter is simply not: unless an instance has authorized fetch (off by default) enabled from day 0 and tracks every instance that requests every post (Mastodon does not), there is no way to know who has a copy of a post that’s moved. After an account migration, there will be stale links. As a result, the discussion around adding post migration has centered on moving posts and just accepting that links will be broken. Maybe just having the posts there on your new account is enough?</p>
<p>I am not familiar enough with the Mastodon codebase to say whether moving to the vision I’ve outlined is feasible. If it’s not, I think Mastodon should continue to pursue alternate methods—if only because of the sheer number of users and the absolute clarity that this is a feature people want. But I think it’s apparent that the approach I’ve outlined here would be a more complete and transparent migration process.</p>
<p>There is, to the best of my knowledge, only one single ActivityPub project that supports multiple domains: <a href="https://github.com/jointakahe/takahe" data-link="github.com/jointakahe/takahe">Takahē</a>. Multiple accounts across different domains being backed by the same host doesn’t get us all the way to portable identity. But the architectural decisions required to support it go a long way towards that vision. I have not taken the time to trawl through the code and work out if it’s actually using the domain to look up AP objects in its database or if it, like Mastodon and others, is still just extracting the database ID from a path component and using that for the lookup. Either way, by virtue of supporting multiple domains already, I think Takahē is much closer to reaching this vision.</p>
<p><strong>Update:</strong> There is an AP implementation, <a href="https://codeberg.org/bovine/bovine/" data-link="codeberg.org/bovine/bovine/">Bovine</a>, which stores and identifies AP objects by their AP identifier, which goes a long way towards making this implementation of portable identity possible.</p>
<p>Migrating existing accounts and content to portable identities, though, is an open and much harder question. The identifier for just about every AP object that’s already out there specifies the host. Making those objects portable would mean either making existing domains identity resolvers rather than hosts (not feasible) or getting every instance to update all of its references to the moved objects (basically where we’re at now, and it’s not feasible either). This is the biggest question regarding this approach, and I readily admit that I do not have a good answer to it.</p>
<h3 id="technical-miscellanea"><a href="#technical-miscellanea" class="header-anchor" aria-hidden="true" role="presentation">###</a> Technical Miscellanea</h3>
<p>What follows isn’t anything cohesive, just some thoughts that occurred while writing this post and thinking about the architecture I’ve described.</p>
<h4 id="so-what-s-the-primary-key-in-my-database-"><a href="#so-what-s-the-primary-key-in-my-database-" class="header-anchor" aria-hidden="true" role="presentation">####</a> So what’s the primary key in my database?</h4>
<p>Well, it can still be whatever you want. And the ActivityPub identifier URL can still be derived therefrom. The key is just that looking up an object uses the <em>entire</em> URL, treating it as an opaque identifier rather than trying to parse it and pull out pieces.</p>
<h4 id="what-data-actually-gets-migrated-"><a href="#what-data-actually-gets-migrated-" class="header-anchor" aria-hidden="true" role="presentation">####</a> What data actually gets migrated?</h4>
<p>Essentially every Activity that you generated on your old host.</p>
<p>Ideally, you’d also want to migrate any relevant activities from other people (think likes/reblogs/replies), in order to preserve the complete context. This can’t be done just by transferring the activities themselves, since that could let importers forge activities from other people. So, what’s actually transferred would need to be a list of IDs of all the relevant activities. The new instance can then dereference them and add them to its databse. This could be a massive collection, and so this part of the import should probably be throttled and done in the background.</p>
<p>Attachments are another wrinkle, given Mastodon’s approach of transcoding everything that’s uploaded. The simplest approach, I think, is that the import process shouldn’t transcode anything unless it exceeds the usual instance-defined limits. In the common case (migrating from one instance to another using the same software) no transcoding or conversion should be necessary.</p>
<h4 id="how-do-you-keep-my-old-instance-from-impersonating-me-"><a href="#how-do-you-keep-my-old-instance-from-impersonating-me-" class="header-anchor" aria-hidden="true" role="presentation">####</a> How do you keep my old instance from impersonating me?</h4>
<p>ActivityPub actors already have public/private keypairs and any activities delivered to other servers have to be signed with your private key, which is then validated by the recipients. As part of the migration, the new host can regenerate your keys, so anything the old instance forges and tries to publish as you will fail validation.</p>
<h4 id="why-not-dids-"><a href="#why-not-dids-" class="header-anchor" aria-hidden="true" role="presentation">####</a> Why not DIDs?</h4>
<p>ATProto uses <a href="https://www.w3.org/TR/did-core/" data-link="w3.org/TR/did-core/">DIDs</a>, rather than URIs, for identifiers. DIDs seem interesting, if quite complicated. The requirement of the ActivityPub spec that identifier URIs’ authorities belong to “their originating server” does not <em>seem</em> to preclude using DIDs as AP identifiers. The primary advantage DIDs confer is that they let you migrate between not just hosts/PDS’s but usernames: he same underlying DID can be updated to refer to <code>@my.domain</code> from <code>@someone.bsky.social</code>. </p>
<aside>
<p>“Doesn’t this just move the centralization point from identity resolver to the DID resolver?” you may ask. Yes, I think it does, but that matters a lot less if your DID is <code>did:plc:&lt;giant random string&gt;</code>. And moreover, the DID resolution process is deliberately left unspecified, but the spec does consider the possibility of multiple resolvers.</p>
</aside>
<p>This does solve the caveat mentioned earlier, that the shared identity resolver has to be treated as infrastructure and be above moderation decisions. But, if the goal is to move the existing ecosystem towards portable identity in a reasonably expendient manner—and I believe that is the goal—adopting DIDs in the short term is unnecessary.</p>
<h4 id="moderating-actions-against-hosts"><a href="#moderating-actions-against-hosts" class="header-anchor" aria-hidden="true" role="presentation">####</a> Moderating Actions Against Hosts</h4>
<p>A very good point brought up in reply to this post was that since, right now, a domain/host/instance are all one and the same, they serve as a very useful target for moderation actions, but portable identity seems to interfere with that. If the moderators of a certain instance condone bad behavior from one person, another instance can take action against that entire instance, rather than just the individual, on the reasonable assumption the moderators will permit similar behavior from other people. But adding the layer of indirection I described makes it much harder to take such actions. Where as now it’s clear that <code>@alice@example.com</code> and <code>@bob@example.com</code> are hosted at the same place, if they used their own domains—say, <code>@alice@alices.place</code> and <code>@bob@bob.online</code>—it’s no longer self-evident that they’re hosted, and thus moderated, at the same place.</p>
<p>First off, I think that portable identity inherently makes this sort of moderation against the host less useful. Making it easier for everyone to up and move hosts definitionally makes it easier for bad actors to do the same. Taking moderation action against <code>example.com</code> has less utility if it’s easy for everyone hosted there to relocate.</p>
<p>But, that said, I think this model is still possible—and for one method we need only look to email, where identities (email addresses) are already separate from hosts (SMTP servers). Hosts could require that inbound activities specify the <em>host</em> that they originate from, not just the actor, and then reject activities from blocked domains. To prevent the originating host from being spoofed, the host would sign the message with a private key and then the recipient would then validate the signature against the originating host’s public key looked up from DNS.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>In principle, at least. As of writing this, there are no other PDS’s that one could move to. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>You can argue that actually social media is or should be ephemeral and people shouldn’t be so attached to their posts. And you might be right. But they are, so arguing about it or trying to convince them otherwise is a waste of time. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>Mastodon &amp; co. have the ability to move a social graph using the <code>Move</code> activity, which indicates that the actor has moved to a new location. This technique, however, it’s not especially reliable. It requires the active participation of <em>everyone</em> in your social graph (or rather, their servers). It foists onto them what should be an implementation detail of your identity. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>You can argue until the cows come home that everyone <em>should</em> have their own domain name. But, again, that’s not the world we live in. If you want to advance the goals of decentralized social networking, rather than concentrating on ideological purity, you have to be pragmatic. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Theming iOS Apps is Still Hard</title>
      <link>https://shadowfacts.net/2023/theming-ios-apps/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/theming-ios-apps/</guid>
      <pubDate>Mon, 20 Mar 2023 22:40:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Sorry to be the bearer of bad news. Last year, Christian Selig wrote <a href="https://christianselig.com/2022/02/difficulty-theming-ios/" data-link="christianselig.com/2022/02/difficulty-th…">a blog post</a> about the annoyances of theming iOS apps. I won’t retread his entire article, but the gist of it is that there is no nice way to easily apply a theme to an entire iOS app. Either every view/controller has to listen for theme change notifications and update itself, or you have to resort to hacky workarounds to force all colors to update<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>.</p>
<!-- excerpt-end -->
<p>The ideal is something that SwiftUI gets right: the environment. You put any values you want in, you can read them at any point in the view hierarchy and changes automatically propagate down. Unfortunately, UIKit has no similar mechanism. Or does it?</p>
<p>UIKit does have a way of defining colors that react to certain changes in their environment—specifically, anything in the trait collection. This works by providing a closure <a href="https://developer.apple.com/documentation/uikit/uicolor/3238041-init" data-link="developer.apple.com/documentation/uikit/…">to UIColor</a> which the framework runs when it needs the concrete color. But it’s just an ordinary closure, so why can’t you base the decision on something else, such as the user’s preferences?</p>
<p>If you did so, you could just use your dynamic colors everywhere, and they’d use the right colors based on the selected theme. And when the user’s preferences changed, you’d just need to tell the system the trait collection changed, and it would handle re-resolving all the dynamic colors.</p>
<p>The word “just” there is doing a lot of heavy lifting, though. I initially went spelunking through UIKitCore to try and find a way of forcing a dynamic color update, and found a promising lead. There’s a very attractively named <code>-[UITraitCollection hasDifferentColorAppearanceComparedToTraitCollection:]</code> method, which sounded exactly like what I was after. Alas, swizzling it was to no avail. The code path the system used when the appearance changed seem to go straight to an internal, non-Objective-C (and thus, not swizzlable) function <code>__UITraitCollectionTraitChangesAlteredEffectiveColorAppearance</code>.</p>
<p>But, in doing all this, I found another approach that seemed even better. <code>UITraitCollection</code> has a <code>_clientDefinedTraits</code> dictionary. And, as the name implies, this goes a long way towards the ideal of the SwiftUI environment in UIKit.</p>
<p>To be clear: this has significant drawbacks. It’s relying on private API that could change at any time. I’m only comfortable doing so because I’m careful to catch Objective-C exceptions whenever I’m accessing it and I’m only using it for a small feature: a preference for switching between the regular iOS pure-black dark mode style, and a non-pure-black one. The failure mode is that the app is still in dark mode, but slightly darker than intended. If themes were a more central aspect of my app, I wouldn’t want to rely on this.</p>
<p>With a simple extension on <code>UITraitCollection</code>, you can make it look like any other property:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">UITraitCollection</span> {
    <span class="hl-kw">var</span> pureBlackDarkMode: <span class="hl-type">Bool</span> {
        <span class="hl-kw">get</span> {
            (<span class="hl-fn">value</span>(forKey: <span class="hl-str">"_clientDefinedTraits"</span>) <span class="hl-kw">as</span>? [<span class="hl-type">String</span>: <span class="hl-type">Any</span>])?[<span class="hl-str">"tusker_usePureBlackDarkMode"</span>] <span class="hl-kw">as</span>? <span class="hl-type">Bool</span> ?? <span class="hl-kw">true</span>
        }
        <span class="hl-kw">set</span> {
            <span class="hl-kw">var</span> dict = <span class="hl-fn">value</span>(forKey: <span class="hl-str">"_clientDefinedTraits"</span>) <span class="hl-kw">as</span>? [<span class="hl-type">String</span>: <span class="hl-type">Any</span>] ?? [:]
            dict[<span class="hl-str">"tusker_usePureBlackDarkMode"</span>] = newValue
            <span class="hl-fn">setValue</span>(dict, forKey: <span class="hl-str">"_clientDefinedTraits"</span>)
        }
    }
    
    <span class="hl-kw">convenience init</span>(pureBlackDarkMode: <span class="hl-type">Bool</span>) {
        <span class="hl-kw">self</span>.<span class="hl-kw">init</span>()
        <span class="hl-kw">self</span>.<span class="hl-prop">pureBlackDarkMode</span> = pureBlackDarkMode
    }
}
</code></pre>
<p>And a dynamic color can read it just like any other trait:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">UIColor</span> {
    <span class="hl-kw">static let</span> appBackground = <span class="hl-type">UIColor</span> { traitCollection <span class="hl-kw">in
        if case</span> .<span class="hl-prop">dark</span> = traitCollection.<span class="hl-prop">userInterfaceStyle</span>,
           !traitCollection.<span class="hl-fn">pureBlackDarkMode</span> {
            <span class="hl-kw">return</span> <span class="hl-type">UIColor</span>(hue: <span class="hl-num">230</span>/<span class="hl-num">360</span>, saturation: <span class="hl-num">23</span>/<span class="hl-num">100</span>, brightness: <span class="hl-num">10</span>/<span class="hl-num">100</span>, alpha: <span class="hl-num">1</span>)
        } <span class="hl-kw">else</span> {
            <span class="hl-kw">return</span> .<span class="hl-prop">systemBackground</span>
        }
    }
}
</code></pre>
<p>Applying it is fairly straightforward: you just use <code>setOverrideTraitCollection(_:forChild:)</code> on a container view controller that contains the app UI. The only wrinkle is that this means the custom trait doesn’t propagate to modally-presented view controllers. Presentation is always performed by the window’s root VC, and so it doesn’t inherit the child’s traits.</p>
<p>But fret not, for another little bit of private API saves the day: <code>UIWindow._rootPresentationController</code> is actually responsible for the presentation, and since it’s a <code>UIPresentationController</code>, you can use the regular, public <code>overrideTraitCollection</code> API. When setting up the window, or when the user’s preference changes, adjust the override collection and it will apply to presented VCs.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">if let</span> rootPresentationController = window.<span class="hl-fn">value</span>(forKey: <span class="hl-str">"_rootPresentationController"</span>) <span class="hl-kw">as</span>? <span class="hl-type">UIPresentationController</span> {
    rootPresentationController.<span class="hl-prop">overrideTraitCollection</span> = <span class="hl-type">UITraitCollection</span>(...)
}
</code></pre>
<p>I’ll end by reiterating that this is all a giant hack and echoing Christian’s sentiment that hopefully iOS <del>16</del> 17 will introduce a proper way of doing this.</p>
<p><strong>Update:</strong> <a href="/2023/custom-traits/" data-link="/2023/custom-traits/">Hell yeah</a></p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>What’s more, the workaround of switching back and forth between user interface styles he describes doesn’t even work reliably. Apparently, only <a href="https://mastodon.social/@christianselig/109790040419220489" data-link="mastodon.social/@christianselig/10979004…">changing the color gamut</a> for the entire app worked. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Calling Swift from Rust</title>
      <link>https://shadowfacts.net/2023/rust-swift/</link>
      <category>rust</category>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/rust-swift/</guid>
      <pubDate>Sun, 19 Feb 2023 01:29:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>From the person that brought you <a href="/2022/swift-rust/" data-link="/2022/swift-rust/">calling Rust from Swift</a> comes the thrilling<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, action<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>-packed sequel: calling Swift from Rust! For a <a href="/2023/rewritten-in-rust/" data-link="/2023/rewritten-in-rust/">recent project</a>, I found myself needing to call into Swift from a Rust project (on both macOS and Linux) and so am documenting here in case you, too, are in this unenviable situation.</p>
<!-- excerpt-end -->
<p>There are unfortunately few options for parsing and syntax highlighting Swift from Rust code. There does exist a Swift grammar for Tree Sitter, which I use for the rest of the new version of my blog, but using it is incredibly slow—just parsing the highlight query took upwards of 5 seconds. What’s more, it doesn’t produce especially good highlighting results. Since Swift accounts for a substantial portion of what I write about on here, that wasn’t tenable.</p>
<p>The best Swift highlighter I know of is John Sundell’s <a href="https://github.com/johnsundell/Splash" data-link="github.com/johnsundell/Splash">Splash</a> library. But, since it’s written in Swift, using it from my new blog engine is rather more complicated than ideal.</p>
<p>There is an existing package called <a href="https://github.com/Brendonovich/swift-rs" data-link="github.com/Brendonovich/swift-rs">swift-rs</a>, however it doesn’t work for this use case. It relies in no small part on Objective-C and its runtime for transferring values across the FFI boundary. My blog is hosted on a Linux machine, where no such runtime is present, so just using that crate is a no-go.</p>
<p>First off is the Swift package. This can be fairly simple, since it just exposes a single function annotated with <code>@_cdecl</code>. The only complication is that, since the Swift function is exposed to C, its type signature must be expressible in C. That means no complex Swift types, like String. But since the goal of this is syntax highlighting, passing a string across the FFI boundary is integral. So, strings are passed as a pointer to the underlying byte buffer and a length.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">@_cdecl</span>(<span class="hl-str">"highlight_swift"</span>)
<span class="hl-kw">public func</span> highlight(codePtr: <span class="hl-type">UnsafePointer</span>&lt;<span class="hl-type">UInt8</span>&gt;, codeLen: <span class="hl-type">UInt64</span>, htmlLenPtr: <span class="hl-type">UnsafeMutablePointer</span>&lt;<span class="hl-type">UInt64</span>&gt;) -&gt; <span class="hl-type">UnsafeMutablePointer</span>&lt;<span class="hl-type">UInt8</span>&gt; {
}
</code></pre>
<p>Reading the input is accomplished by turning the base pointer and length into a buffer pointer, turning that into a <code>Data</code>, and finally into a <code>String</code>. Unfortunately, there are no zero-copy initializers<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>, so this always copies its input. Being in a Rust mindset, I really wanted to get rid of this copy, but there doesn’t seem to be an obvious way, and at the end of the day, it’s not actually a problem.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> buf = <span class="hl-type">UnsafeBufferPointer</span>(start: codePtr, count: <span class="hl-type">Int</span>(codeLen))
<span class="hl-kw">let</span> data = <span class="hl-type">Data</span>(buffer: buf)
<span class="hl-kw">let</span> code = <span class="hl-type">String</span>(data: data, encoding: .<span class="hl-prop">utf8</span>)!
</code></pre>
<p>You may notice that the code string length is being passed into the function as an unsigned 64-bit integer, and then being converted to an <code>Int</code>, which may not be capable of representing the value. But, since this is just a syntax highlighter for my blog, there’s absolutely no chance of it ever being used to highlight a string longer than 2<sup>63</sup>-1 bytes.</p>
<p>The actual highlighting I’ll skip, you can refer to the documentation for Splash. Once that’s done, though, the output needs to be sent back to Rust somehow. Again, since the function signature needs to be compatible with C, it returns a pointer to a byte buffer containing the UTF-8 encoded string. It also sets a length pointer provided by the caller to the length in bytes of the output.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> html = <span class="hl-cmt">// ...</span>
<span class="hl-kw">let</span> outPtr = <span class="hl-type">UnsafeMutableBufferPointer</span>&lt;<span class="hl-type">UInt8</span>&gt;.<span class="hl-fn">allocate</span>(capacity: html.<span class="hl-prop">utf8</span>.<span class="hl-prop">count</span>)
<span class="hl-kw">_</span> = html.<span class="hl-fn">withUTF8</span> { buf <span class="hl-kw">in</span>
    buf.<span class="hl-fn">copyBytes</span>(to: outPtr, count: buf.<span class="hl-prop">count</span>)
}
htmlLenPtr.<span class="hl-prop">pointee</span> = <span class="hl-type">UInt64</span>(outPtr.<span class="hl-prop">count</span>)
<span class="hl-kw">return</span> outPtr.<span class="hl-prop">baseAddress</span>!
</code></pre>
<p>Note that the <code>html</code> string is declared as variable, since <code>withUTF8</code> may mutate it if the backing storage is not already contiguous.</p>
<p>The Swift code allocates a buffer of the appropriate length, and copies the data into it. By itself, this would leak, but the Rust code on the other side of the FFI boundary will take ownership of it and then deallocate it as usual when the string is dropped.</p>
<p>Invoking the Swift function on the Rust is is fairly straightforward. First the external function needs to be declared:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">extern</span> <span class="hl-str">&quot;C&quot;</span> {
    <span class="hl-kw">fn</span> <span class="hl-fn">highlight_swift</span>(<span class="hl-var">code_ptr</span>: <span class="hl-op">*</span><span class="hl-kw">const</span> <span class="hl-builtin">u8</span>, <span class="hl-var">code_len</span>: <span class="hl-builtin">u64</span>, <span class="hl-var">html_len_ptr</span>: <span class="hl-op">*</span><span class="hl-kw">mut</span> <span class="hl-builtin">u64</span>) -&gt; <span class="hl-op">*</span><span class="hl-kw">mut</span> <span class="hl-builtin">u8</span>;
}
</code></pre>
<p>Then, there’s a little wrapper function that provides a more Rust-y interface, rather than the actual client having to deal with raw pointers:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">pub</span> <span class="hl-kw">fn</span> <span class="hl-fn">highlight</span>(<span class="hl-var">code</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>) -&gt; <span class="hl-type">String</span> {
    <span class="hl-kw">unsafe</span> {
        <span class="hl-kw">let</span> <span class="hl-kw">mut</span> html_len: <span class="hl-builtin">u64</span> = <span class="hl-const">0</span>;
        <span class="hl-kw">let</span> html_ptr = <span class="hl-fn">highlight_swift</span>(code.<span class="hl-fn">as_ptr</span>(), code.<span class="hl-fn">len</span>() <span class="hl-op">as</span> <span class="hl-builtin">u64</span>, <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> html_len);
        <span class="hl-type">String</span>::<span class="hl-fn">from_raw_parts</span>(html_ptr, html_len <span class="hl-op">as</span> <span class="hl-builtin">usize</span>, html_len <span class="hl-op">as</span> <span class="hl-builtin">usize</span>)
    }
}
</code></pre>
<p><a href="https://doc.rust-lang.org/std/string/struct.String.html#method.from_raw_parts" data-link="doc.rust-lang.org/std/string/struct.Stri…"><code>String::from_raw_parts</code></a> takes the base pointer, the length, and the buffer capacity and produces an owned <code>String</code> that uses that buffer as its storage, with the given length and capacity. This does require that the buffer be managed by the same allocator as Rust’s global one, but here everything is using the system <code>malloc</code>, so it’s safe.</p>
<p>Next, comes actually building this thing. First, the Swift package product needs to be changed to be a static library:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> package = <span class="hl-type">Package</span>(
    <span class="hl-cmt">// ...</span>
    products: [
        .<span class="hl-fn">library</span>(
            name: <span class="hl-str">"highlight-swift"</span>,
            type: .<span class="hl-prop">static</span>,
            targets: [<span class="hl-str">"highlight-swift"</span>]
        )
    ],
    <span class="hl-cmt">// ...</span>
)
</code></pre>
<p>Then, comes a whole bunch of stuff in the <code>build.rs</code> script of the Rust wrapper crate. Before I get into it, I want to note that much of this is based off the work of the <a href="https://github.com/Brendonovich/swift-rs" data-link="github.com/Brendonovich/swift-rs">swift-rs</a> project.</p>
<p>First comes a bunch of stuff to get the macOS build working. This part is cribbed from swift-rs, albeit simplified to only do exactly what I need. It needs to emit instructions for the Rust compiler to link against the Swift standard library as well as compile the Swift package and link against it too.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
    <span class="hl-fn">link_swift</span>();
    <span class="hl-fn">link_swift_package</span>(<span class="hl-str">&quot;highlight-swift&quot;</span>, <span class="hl-str">&quot;./highlight-swift/&quot;</span>);
}

<span class="hl-kw">fn</span> <span class="hl-fn">link_swift</span>() {
    <span class="hl-kw">let</span> swift_target_info = <span class="hl-fn">get_swift_target_info</span>();

    swift_target_info
        .<span class="hl-prop">paths</span>
        .<span class="hl-prop">runtime_library_paths</span>
        .<span class="hl-fn">iter</span>()
        .<span class="hl-fn">for_each</span>(|path| {
            <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-search=native={}&quot;</span>, path);
        });
}

<span class="hl-kw">fn</span> <span class="hl-fn">link_swift_package</span>(<span class="hl-var">package_name</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>, <span class="hl-var">package_root</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>) {
    <span class="hl-kw">let</span> profile = env::<span class="hl-fn">var</span>(<span class="hl-str">&quot;PROFILE&quot;</span>).<span class="hl-fn">unwrap</span>();

    <span class="hl-kw">if</span> !<span class="hl-type">Command</span>::<span class="hl-fn">new</span>(<span class="hl-str">&quot;swift&quot;</span>)
        .<span class="hl-fn">args</span>(<span class="hl-op">&amp;</span>[<span class="hl-str">&quot;build&quot;</span>, <span class="hl-str">&quot;-c&quot;</span>, <span class="hl-op">&amp;</span>profile])
        .<span class="hl-fn">current_dir</span>(package_root)
        .<span class="hl-fn">status</span>()
        .<span class="hl-fn">unwrap</span>()
        .<span class="hl-fn">success</span>()
    {
        <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;Failed to compile swift package {}&quot;</span>, package_name);
    }

    <span class="hl-kw">let</span> swift_target_info = <span class="hl-fn">get_swift_target_info</span>();
    
    <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-search=native={}.build/{}/{}&quot;</span>, package_root, swift_target_info.unversioned_triple, profile);
    <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-lib=static={}&quot;</span>, package_name);
}
</code></pre>
<p>This relies on another supporting function from swift-rs, <code>get_swift_target_info</code>, which parses the output of <code>swift -print-target-info</code> to get information about the current target and location of the Swift stdlib. Note that this also requires <code>serde</code> and <code>serde_json</code> to be added to the <code>[build-dependencies]</code> section of <code>Cargo.toml</code>.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">get_swift_target_info</span>() -&gt; <span class="hl-type">SwiftTarget</span> {
    <span class="hl-kw">let</span> swift_target_info_str = <span class="hl-type">Command</span>::<span class="hl-fn">new</span>(<span class="hl-str">&quot;swift&quot;</span>)
        .<span class="hl-fn">args</span>(<span class="hl-op">&amp;</span>[<span class="hl-str">&quot;-print-target-info&quot;</span>])
        .<span class="hl-fn">output</span>()
        .<span class="hl-fn">unwrap</span>()
        .<span class="hl-prop">stdout</span>;
    serde_json::<span class="hl-fn">from_slice</span>(<span class="hl-op">&amp;</span>swift_target_info_str).<span class="hl-fn">unwrap</span>()
}

<span class="hl-attr">#[derive(Deserialize)]</span>
<span class="hl-kw">struct</span> <span class="hl-type">SwiftTarget</span> {
    <span class="hl-prop">target</span>: <span class="hl-type">SwiftTargetInfo</span>,
    <span class="hl-prop">paths</span>: <span class="hl-type">SwiftPaths</span>,
}

<span class="hl-attr">#[derive(Deserialize)]</span>
<span class="hl-kw">struct</span> <span class="hl-type">SwiftTargetInfo</span> {
    <span class="hl-prop">unversioned_triple</span>: <span class="hl-type">String</span>,
    <span class="hl-attr">#[serde(rename = <span class="hl-str">&quot;librariesRequireRPath&quot;</span>)]</span>
    <span class="hl-prop">libraries_require_rpath</span>: <span class="hl-builtin">bool</span>,
}

<span class="hl-attr">#[derive(Deserialize)]</span>
<span class="hl-kw">struct</span> <span class="hl-type">SwiftPaths</span> {
    <span class="hl-prop">runtime_library_paths</span>: <span class="hl-type">Vec</span>&lt;<span class="hl-type">String</span>&gt;,
    <span class="hl-prop">runtime_resource_path</span>: <span class="hl-type">String</span>,
}
</code></pre>
<p>This is enough to get everything to compile, link, and run on macOS. But unsurprisingly, running on Linux won’t yet work. The macOS dynamic linker, dyld, is doing a lot of heavy lifting: at runtime it will dynamically link in the Swift standard library and make sure the Swift runtime is initialized. But the same is not the case on Linux, so the build script also needs to tell Rust which libraries to link against.</p>
<p>First, the Swift standard library paths need to be included in the Rust target’s rpath, so that the dynamic libraries there will be located at runtime.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">let</span> target = <span class="hl-fn">get_swift_target_info</span>();
    <span class="hl-kw">if</span> target.<span class="hl-prop">target</span>.<span class="hl-prop">unversioned_triple</span>.<span class="hl-fn">contains</span>(<span class="hl-str">&quot;linux&quot;</span>) {
        target.<span class="hl-prop">paths</span>.<span class="hl-prop">runtime_library_paths</span>.<span class="hl-fn">iter</span>().<span class="hl-fn">for_each</span>(|path| {
            <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-arg=-Wl,-rpath={}&quot;</span>, path);
        });
    }
}
</code></pre>
<p>Then come the instructions to link against the various components of the Swift standard library. Not all of these are likely necessary, but rather than try to figure out which were, I opted to just link against all of of the <code>.so</code>s in the <code>/usr/lib/swift/linux</code> directory inside the Swift install.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> target.<span class="hl-prop">target</span>.<span class="hl-prop">unversioned_triple</span>.<span class="hl-fn">contains</span>(<span class="hl-str">&quot;linux&quot;</span>) {
        <span class="hl-cmt">// ...</span>
        <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-lib=dylib=BlocksRuntime&quot;</span>);
        <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-lib=dylib=dispatch&quot;</span>);
        <span class="hl-cmt">// etc.</span>
    }
}
</code></pre>
<p>Finally, the <code>swiftrt.o</code> object file, which actually initializes the runtime (needed for just about any non-trivial Swift code).</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">if</span> target.<span class="hl-prop">target</span>.<span class="hl-prop">unversioned_triple</span>.<span class="hl-fn">contains</span>(<span class="hl-str">&quot;linux&quot;</span>) {
        <span class="hl-cmt">// ...</span>
        <span class="hl-kw">let</span> arch = env::<span class="hl-fn">var</span>(<span class="hl-str">&quot;CARGO_CFG_TARGET_ARCH&quot;</span>).<span class="hl-fn">unwrawp</span>();
        <span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cargo:rustc-link-arg={}/linux/{}/swiftrt.o&quot;</span>, &amp;target.paths.runtime_resource_path, arch);
    }
}
</code></pre>
<p>I’m doing this by just passing the object file as a rustc link argument, since the linker will interpret it properly, but I’m not sure if there’s a better way of telling rustc I want this object file included. Unfortunately, this method means that <code>swiftrt.o</code> isn’t included when this crate is compiled into another one (and nor is the rpath linker flag used by the outer binary). So, in my actual blog crate, there’s some <a href="https://git.shadowfacts.net/shadowfacts/v6/src/commit/c25100692be02b8293125450c34e8963c643f0c9/build.rs" data-link="git.shadowfacts.net/shadowfacts/v6/src/c…">very similar code</a>.</p>
<p>And, at long last, that is enough to run the program on Linux, with the Rust code calling into Swift and getting the result back as expected. If you want to see the entire project, it can be found <a href="https://git.shadowfacts.net/shadowfacts/splash-rs/" data-link="git.shadowfacts.net/shadowfacts/splash-r…">on my Gitea</a>.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>“Thrilling” is here defined as “confounding”. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Herein, “action” refers to linker errors. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>There is a <a href="https://developer.apple.com/documentation/swift/string/init(bytesnocopy:length:encoding:freewhendone:)" data-link="developer.apple.com/documentation/swift/…"><code>bytesNoCopy</code></a> initializer, but it’s deprecated and the documentation notes that Swift doesn’t support zero-copy initialization. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Tusker is Now Available</title>
      <link>https://shadowfacts.net/2023/tusker/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2023/tusker/</guid>
      <pubDate>Mon, 23 Jan 2023 16:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><a data-no-link-decoration href="https://apps.apple.com/us/app/tusker/id1498334597?itscg=30200&amp;itsct=apps_box_appicon" style="width: 170px; height: 170px; border-radius: 22%; overflow: hidden; display: block; vertical-align: middle; margin: 0 auto;"><img src="https://is5-ssl.mzstatic.com/image/thumb/Purple123/v4/15/b8/df/15b8df2d-861e-21bd-1e06-5bf7e7f7962f/AppIcon-1x_U007emarketing-0-0-0-7-0-0-85-220.png/540x540bb.jpg" alt="Tusker" style="width: 170px; height: 170px; border-radius: 22%; overflow: hidden; display: inline-block; vertical-align: middle;"></a></p>
<p>I am very excited to announce that after almost four and a half years of development, Tusker, my iOS app for Mastodon is now available on the <a href="https://apps.apple.com/us/app/tusker/id1498334597" data-link="apps.apple.com/us/app/tusker/id149833459…">App Store</a>!</p>
<p>If you follow my blog or follow me on the fediverse, you’ve no doubt heard me talk at length about it before, so I’ll spare you the details here. Suffice it to say that Tusker is a completely native iOS app that supports many of the latest features of both Mastodon and iOS.</p>
<p>This is not the end of development, there are still lots of features I plan to add—including push notifications and the ability to edit posts. If you’re already in the beta via TestFlight, you’re more than welcome to remain there. It will continue to get beta updates with new features and bugfixes ahead of the App Store releases.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Rewritten in Rust</title>
      <link>https://shadowfacts.net/2023/rewritten-in-rust/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2023/rewritten-in-rust/</guid>
      <pubDate>Thu, 05 Jan 2023 19:30:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>So, about six months ago I decided I wanted to rewrite my perfectly-working blog backend in Rust. Why? Because I was bored and wanted an excuse to use Rust more.</p>
<!-- excerpt-end -->
<p>The fundamental architecture of my site is unchanged from the last <a href="/2019/reincarnation" data-link="/2019/reincarnation">rewrite</a>. All of the HTML pages are generated up front and written to disk. The HTTP server can then handle any ActivityPub-specific requests and fall back to serving files straight from disk.</p>
<blockquote class="pull right">
i look forward to finishing this rewrite and then being able to sit back and enjoy... *checks notes* the exact same website i had before
</blockquote>
<p>Because this project was undertaken with the deliberate goal of using Rust more, I let myself spend more time bikeshedding and working on pieces that I ordinarily would have ignored or left to 3rd party libraries. One of those was spending probably too much time writing a bunch of code to slugify post titles—that is, turning titles with lots of punctuation and things into a nice and URL-safe format. It handles a bunch of pet peeves I have when I look at URLs on other websites (e.g., non-ASCII characters geting blindly replaced resulting in long sequences of hyphens), even if those are highly unlikely to ever arise here.</p>
<p>Another component I spent a great deal of time working on was the Markdown to HTML rendering. I’m using the <a href="https://lib.rs/crates/pulldown-cmark" data-link="lib.rs/crates/pulldown-cmark">pulldown-cmark</a> crate, which handles a great deal for me, but not quite everything. In the previous implementation of my blog, I was using the <a href="https://github.com/markdown-it/markdown-it" data-link="github.com/markdown-it/markdown-it">markdown-it</a> package which has some other little niceties to make the generated HTML better, in addition to the custom plugins I was using for the Markdown decorations you see if you’re reading this on my blog itself. I have custom code for handling the link and heading decorations, as before. I also override how footnote definitions are generated, to make them all appear at the end of the HTML rather appearing in the same locations they’re defined in the Markdown. As part of the changes to footnote definitions, I also generate backlinks which go from the footnote back to where it was referenced, to make reading them a bit easier.</p>
<h2 id="syntax-highlighting"><a href="#syntax-highlighting" class="header-anchor" aria-hidden="true" role="presentation">##</a> Syntax Highlighting</h2>
<p>The previous, Node.js implementation of my blog used highlight.js for syntax highlighting. This works decently well, but it doesn’t produce the most accurate highlighting since it’s essentially a big pile of regexes rather than parsing the syntax. Now, I’m using <a href="https://tree-sitter.github.io/tree-sitter/" data-link="tree-sitter.github.io/tree-sitter/">Tree Sitter</a> which actually parses the language and so highlighting ends up being more accurate.</p>
<p>Unfortunately, software being what it is, this is not always the case. The syntax highlighting for Swift with the best available Tree Sitter grammar is substantially worse than the highlight.js results. It routinely misses keywords, misinterprets variables as functions, and—to top all that off—something about how the highlight query is structured is incredibly slow for Tree Sitter to load. It more than doubles the time it takes to generate my blog, from about 0.5 seconds when skipping Swift highlighting to 1.3s when using tree-sitter-swift.</p>
<p>So, because I’ve never met a problem I couldn’t yak-shave, I decided the solution was to use John Sundell’s <a href="https://github.com/johnsundell/Splash" data-link="github.com/johnsundell/Splash">Splash</a> library for highlighting Swift snippets. A number of Swift blogs I follow use it, and it seems to produce very good results. But, of course, it’s written in Swift, so I need some way of accessing it from Rust. This was a little bit of an ordeal, and it ended up being very easy, then very difficult, then easy, then confusing, and finally not too bad. The details of how exactly everything works and what I went through are a subject for <a href="/2023/rust-swift/" data-link="/2023/rust-swift/">another time</a>, but if you want to see how it works, the source code for the Rust/Swift bridge is <a href="https://git.shadowfacts.net/shadowfacts/splash-rs/src/branch/main" data-link="git.shadowfacts.net/shadowfacts/splash-r…">available here</a>.</p>
<h2 id="activity-pub"><a href="#activity-pub" class="header-anchor" aria-hidden="true" role="presentation">##</a> ActivityPub</h2>
<p>One of the big features from the last time I rewrote my blog was the <a href="https://www.w3.org/TR/activitypub/" data-link="w3.org/TR/activitypub/">ActivityPub</a> integration. Almost nobody uses it, but I think it’s a cool feature and so I wanted to keep it. I’m using the <a href="https://lib.rs/crates/activitystreams" data-link="lib.rs/crates/activitystreams">activitystreams</a> crate, and what I’ve learned is that statically typed languages are maybe not so great for writing AP implementations. Lots of properties can be multiple different types, so you can’t directly read anything, you have to check that it is in fact the type you expect. This does make for a more correct implementation, but it ends up being a big pain in the ass.</p>
<aside>
<p>ActivityPub and ActivityStreams 2 are both incredibly abstract and generic specifications, so trying to use them from statically typed languages ends up being more than a little bit cumbersome. Unfortunately, writing a relatively complete and widely compatible AP implementation is a complex enough task that writing one in a dynamically typed language is also a bit of a hassle. Maybe the move is to use a dynamically typed language and just have very good test coverage from the get-go.</p>
</aside>
<p>ActivityPub support was undoubtedly the part of this project that took the most time. Just about all of the static generator was complete within a couple weeks. The AP support dragged on over the next 6 months because it was so unpleasant (both for the aforementioned reasons, and just that dealing with all the quirks of different AP implementations is a pain).</p>
<p>But, now that it’s all done, I’m pretty happy with where it is. The ActivityPub support is mostly unchanged. Blog posts are still AP <code>Article</code> objects that you can interact with and comment on, and you can still follow the blog AP actor from Mastodon/etc. to get new posts to show up in your home feed. I did make a couple small quality-of-life changes:</p>
<ol>
<li>If you reply to a blog post with a non-public post, you’ll get an automated reply back telling you that’s not supported. The purpose of replying to a blog post is to make comments show up, and I don’t want to display things that people didn’t intend to be public. If you want to send a private comment, you can message me directly, rather than the blog itself.</li>
<li>When there are new comments, the blog will automatically send me a notification (via AP, of course) with a digest of new posts. Previously, I had to manually check if there were any comments (if you ever commented and I never noticed it, sorry).</li>
</ol>
<h2 id="misc"><a href="#misc" class="header-anchor" aria-hidden="true" role="presentation">##</a> Misc</h2>
<p>The only other notable change is the addition of the <a href="/tv/" data-link="/tv/">TV</a> section, which is an archive of the various long-running commentary threads I’ve written on Mastodon.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Live Activities (and Bad Apple)</title>
      <link>https://shadowfacts.net/2022/live-activities/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/live-activities/</guid>
      <pubDate>Sat, 01 Oct 2022 18:05:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I recently got <a href="https://xkcd.com/356/" data-link="xkcd.com/356/">nerd sniped</a> by <a href="https://twitter.com/zhuowei/status/1573711389285388288" data-link="twitter.com/zhuowei/status/1573711389285…">this tweet</a> from Zhuowei Zhang about playing the Bad Apple video in the Dynamic Island on the iPhone 14 Pro. His original implementation used a webpage and the media session API, and this worked, but the system plays an animation when the artwork changes, so the framerate was limited to 2 FPS. Not ideal for watching a video. So, I wanted to see how much closer to watchable I could get.</p>
<p>This post isn’t going to be a detailed guide or anything, just a collection of some mildly interesting things I learned.</p>
<!-- excerpt-end -->
<p>Before I started this, I was already aware that Live Activity updates had a 4KB limit on the size of the dynamic state. So, the first thing I worked on was encoding the video into a smaller format. Because this particular video is black and white, I encoded it as 1 bit-per-pixel. Each frame is also complete, so the widget doesn’t need access to any previous frames to display the current one.</p>
<p>Since I wanted to display it in the Dynamic Island, the video is also scaled down to be very small, 60 × 45 pixels—one eighth the resolution of the original. This is a convenient size because each row of pixels can be encoded as a single UInt64. The entire frame comes out to 45 × 8 = 360 bytes, which is plenty small.<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup></p>
<p>The whole video is encoded when the app starts up, which takes about 8 seconds. That’s faster than real time (the video is 3m39s), so it could be done during playback, but doing it ahead of time is fast enough that I didn’t feel like putting in the work to optimize a shitpost.</p>
<p>The widget can then unpack the encoded frame into a bitmap that can be turned into an image.</p>
<p>Adding the Live Activity wasn’t difficult—the API is wonderfully straightforward—but, alas, updating it was not so.</p>
<p>While the app was in the foreground, ActivityKit would log a message whenever I asked it to update the activity. But, when the app went into the background, those messages were no longer logged—even though my code was still running and requesting updates. Interestingly, the app is considered to be in the foreground if you open Notification Center while in the app, and so the activity can be updated, which is how this demo came about:</p>
<div>
	<video controls style="max-width: 50%; margin: 0 auto; display: block;" title="The Bad Apple video, with sound, playing back first in an app and then in a Live Activity in the Notification Center.">
		<source src="/2022/live-activities/notif-center.mp4" type="video/mp4">
	</video>
</div>
<p>I scratched my head at the issue of background updates for a while, and tried a couple things to no avail, until I attached Console.app to my phone and filtered for “activity”. At which point, I saw a bunch of messages like these from <code>sessionkitd</code> (which is the system daemon that manages live activities):</p>
<div class="article-content-wide">
	<img src="/2022/live-activities/console.png" alt="com.apple.activitykit sessionkitd xpc Process is playing background media and forbidden to update activity: 984">
</div>
<p>Apps playing background audio seem to be completely forbidden from updating Live Activities. The only possible reason for this I can imagine is to prevent apps from making their own now playing activities, rather than relying on the system one. I don’t know why this is the case, but whatever, back to trying to find workarounds.</p>
<p>At this point, I downloaded the most recent iOS 16 IPSW for the iPhone 14 Pro. After extracting the dyld shared cache from the image (using <a href="https://github.com/blacktop/ipsw" data-link="github.com/blacktop/ipsw">this</a> helpful tool) I started poking around to try and find the code responsible for deciding if an app is allowed to update its activities. Opening the dyld shared cache in Hopper and searching for “session” revealed several promising-sounding frameworks: SessionCore, SessionFoundation, and SessionKit.</p>
<p>SessionCore turned out to be the one containing that log message. But unfortunately, it’s written in Swift and I am not good enough at reverse engineering to decipher what it’s actually doing. I did, however, manage to find a couple tidbits just be looking through the strings in the binary:</p>
<ol>
<li>An entitlement named <code>com.apple.private.sessionkit.backgroundAudioUpdater</code></li>
</ol>
<p>It wasn’t of much use to me, but if someone ever manages to jailbreak these devices, you could have some fun with it.</p>
<ol start="2">
<li>A log message reading “Process looks like a navigation app and can update activity”</li>
</ol>
<p>This looked more promising because the phrasing “looks like” suggests to me that it’s just using a heuristic, rather than determining what the app is for certain. I tried to trick it by adding entitlements for MapKit and location, tracking the user’s location while in the background, and trying various audio session categories that seemed more map-app like. But this effort was to no avail, <code>sessionkitd</code> still forbade me from updating the activity in the background.</p>
<p>At this point, I gave up on getting audio working and just settled for playing the video in the Dynamic Island. I had to scrap the previous implementation of detecting new frames because it used <code>AVPlayer</code> and is therefore incompatible with updating in the background. But, since I have an array of frames, playing it back myself is as simple as using a timer to emit a new one every 1/30th of a second.</p>
<p>With that, I was finally able to play back the video in the island:</p>
<div>
	<video controls style="max-width: 50%; margin: 0 auto; display: block;" title="The Bad Apple video playing back silently in the left section of the Dynamic Island.">
		<source src="/2022/live-activities/island-silent.mp4" type="video/mp4">
	</video>
</div>
<p>You may notice that in both attempts the video appears somewhat blurry. This is becuase iOS animates any changes to the Live Activity view. As with all widgets, the SwiftUI view tree is serialized and then deserialized and displayed in a separate process, so you don’t have direct control over the animation. There is a new <a href="https://developer.apple.com/documentation/swiftui/contenttransition" data-link="developer.apple.com/documentation/swiftu…"><code>ContentTransition</code></a> API that the Live Activity docs say should work, but unfortunately the identity transition “which indicates that content changes should’t animate” has no effect on the activity.</p>
<p>Just having the video in the island was pretty satisfying to see. But, I still really wanted to see the video playing in the island with the audio.</p>
<p>Zhuowei suggested using the system Music app to play back the sound while my app controlled the video. This worked, though it means the app is no longer entirely self-contained. You can use the <code>MPMusicPlayerController.systemMusicPlayer</code> to control the system Music app, and playing back a particular track is simple enough:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> player = <span class="hl-type">MPMusicPlayerController</span>.<span class="hl-prop">systemMusicPlayer</span>
<span class="hl-kw">let</span> query = <span class="hl-type">MPMediaQuery</span>.<span class="hl-fn">songs</span>()
query.<span class="hl-fn">addFilterPredicate</span>(<span class="hl-type">MPMediaPropertyPredicate</span>(value: <span class="hl-str">"badapple"</span>, forProperty: <span class="hl-type">MPMediaItemPropertyTitle</span>))
player.<span class="hl-fn">setQueue</span>(with: query)
<span class="hl-kw">do</span> {
	<span class="hl-kw">try await</span> player.<span class="hl-fn">prepareToPlay</span>()
	player.<span class="hl-fn">play</span>()
} <span class="hl-kw">catch</span> {
	<span class="hl-cmt">// if the song doesn't exist, ignore it</span>
}
</code></pre>
<p>Annoyingly, the only way of getting the track into the Music app on my phone was by disabling iCloud Music Library and syncing it from my Mac. Why iCloud needs to be disabled to let you manually sync files, I do not know—especially seeing as the manually-synced tracks remain on the device even after iCloud is turned back on.</p>
<p>And this, it turns out, is impossible to record because apparently the Music app mutes itself during screen recordings (using the builtin Control Center method, or attaching to QuickTime on a Mac). So, you’ll just have to take my word for it.</p>
<p>This was a fun, if utterly pointless, endeavor. If you want to see the code or run it yourself, it’s available <a href="https://git.shadowfacts.net/shadowfacts/LiveApple" data-link="git.shadowfacts.net/shadowfacts/LiveAppl…">here</a>.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Zhuowei <a href="https://notnow.dev/objects/bddcdb31-7ece-4ae1-86b1-7c5a2d23bec6" data-link="notnow.dev/objects/bddcdb31-7ece-4ae1-86…">pointed out</a> that by using a palette, you could get even get a color frame the same size into 2.8 kilobytes without even getting into any fancy encoding techniques. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>LiveView Native</title>
      <link>https://shadowfacts.net/2022/liveviewnative/</link>
      <category>elixir</category>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/liveviewnative/</guid>
      <pubDate>Thu, 01 Sep 2022 16:30:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’m very excited for the project I’ve been working on all year to finally be public. <a href="https://native.live" data-link="native.live">LiveView Native</a> is a library that lets you build native apps backed by <a href="https://github.com/phoenixframework/phoenix_live_view" data-link="github.com/phoenixframework/phoenix_live…">Phoenix LiveView</a>. I’ve been developing the <a href="https://github.com/liveviewnative/liveview-client-swiftui" data-link="github.com/liveviewnative/liveview-clien…">iOS client</a> which is backed by SwiftUI.</p>
<p>Using LiveView Native lets avoid duplicating business logic on the frontend and save time on implementing dedicated APIs for native apps. The iOS client can be integrated into any existing app by using a single SwiftUI view, so it’s easy to adopt it for just a single screen at a time.</p>
<p>You can find the documentation<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> for the Swift package <a href="https://liveviewnative.github.io/liveview-client-swiftui/documentation/liveviewnative/" data-link="liveviewnative.github.io/liveview-client…">here</a>, including a step-by-step <a href="https://liveviewnative.github.io/liveview-client-swiftui/tutorials/yourfirstapp" data-link="liveviewnative.github.io/liveview-client…">tutorial</a> which walks you through building a complete app with LiveView Native.</p>
<p>We’ve also developed a simple <a href="https://github.com/liveviewnative/elixirconf_chat" data-link="github.com/liveviewnative/elixirconf_cha…">chat app</a> which was used by the attendees of ElixirConf this year, and serves as a complete example of a LiveView Native app.</p>
<p>I’m very excited to see what people build with it.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>This is the first time I’ve used <a href="https://developer.apple.com/documentation/docc" data-link="developer.apple.com/documentation/docc">DocC</a> and it has been largely excellent. It’s made it very easy to produce nice-looking and well-organized documentation. And the tutorial mechanism has been very useful. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Webshit Weekly (2022/08/14)</title>
      <link>https://shadowfacts.net/2022/webshit-weekly/</link>
      <category>misc</category>
      <guid>https://shadowfacts.net/2022/webshit-weekly/</guid>
      <pubDate>Tue, 16 Aug 2022 14:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>An annotated digest of the top “Hacker” “News” posts for the second week of August, 2022.</p>
<p>(A tribute<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> to the seemingly ended webshit weekly series from <a href="http://n-gate.com/" data-link="n-gate.com">n-gate</a>.)</p>
<!-- excerpt-end -->
<style>
.article-content {
	font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', 'VGA' !important;
	font-size: 1.1rem !important;
}
h3, h4 {
	font-family: 'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', 'VGA' !important;
	margin-bottom: 0;
}
h4 {
	margin-top: 0;
}
.article-content a.header-anchor {
	display: none;
}
a::before, a::after {
	content: "" !important;
}
.article-content a {
	text-decoration: underline !important;
}
</style>
<h3 id="to-uncover-a-deepfake-video-call-ask-the-caller-to-turn-sideways"><a href="#to-uncover-a-deepfake-video-call-ask-the-caller-to-turn-sideways" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://metaphysic.ai/to-uncover-a-deepfake-video-call-ask-the-caller-to-turn-sideways/">To uncover a deepfake video call, ask the caller to turn sideways</a></h3><h4 id="august-8-2022-comments-"><a href="#august-8-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 8, 2022 <a href="https://news.ycombinator.com/item?id=32384653">(comments)</a></h4>
<p>Metaphysic (a company that seeks to “empower individuals” with artificial intelligence “content generation” (read: plagiarism laundering) tools) determines that relatively few images of people in big datasets are from a completely in profile. As such, deepfake tools have poor results when compositing someone’s face onto a profile view of a subject. Hackernews helpfully notes that this is merely a present limitation of deepfake and face alignment neural networks, and that within a few short years, these dark days of deepfake detectability will be behind us. Other Hackernews propose a series of solutions that will surely not be defeated by further advancements in deepfake tools.</p>
<h3 id="an-incident-impacting-5m-accounts-and-private-information-on-twitter"><a href="#an-incident-impacting-5m-accounts-and-private-information-on-twitter" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://privacy.twitter.com/en/blog/2022/an-issue-affecting-some-anonymous-accounts">An incident impacting 5M accounts and private information on Twitter</a></h3><h4 id="august-9-2022-comments-"><a href="#august-9-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 9, 2022 <a href="https://news.ycombinator.com/item?id=32399949">(comments)</a></h4>
<p>Twitter (business model: “Uber for bad takes”) informs the public that a flaw in their code let anyone discover which account, if any, a particular email address or phone number belonged to. <a href="https://privacy.twitter.com/en/blog/2020/an-incident-impacting-your-account-identity" data-link="privacy.twitter.com/en/blog/2020/an-inci…">Again</a>. They assure everyone that they take privacy Very Seriously and that <a href="https://www.ftc.gov/news-events/news/press-releases/2022/05/ftc-charges-twitter-deceptively-using-account-security-data-sell-targeted-ads" data-link="ftc.gov/news-events/news/press-releases/…">only they</a> are allowed to use that information for illicit purposes. One Hackernews considers the possibility of enumerating all 10 billion phone numbers. A number of Hackernews also note that Twitter helpfully victim <del>blames</del> suggests their users simply not use a publicly-known phone number to protect against the company’s incompetence.</p>
<h3 id="instagram-can-track-anything-you-do-on-any-website-in-their-in-app-browser"><a href="#instagram-can-track-anything-you-do-on-any-website-in-their-in-app-browser" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://krausefx.com/blog/ios-privacy-instagram-and-facebook-can-track-anything-you-do-on-any-website-in-their-in-app-browser">Instagram can track anything you do on any website in their in-app browser</a></h3><h4 id="august-10-2022-comments-"><a href="#august-10-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 10, 2022 <a href="https://news.ycombinator.com/item?id=32415470">(comments)</a></h4>
<p>Meta™ (business model: “Uber for antitrust complaints”) decides that not only are they entitled to write down everything you do on their <del>websites</del> <del>apps</del> metaspaces, but they are also entitled to spy on you whenever you try to leave them. Hackernews are confused about why Meta™ doesn’t simply use the APIs provided by Apple (business model: “Uber for UI frameworks”) for showing web views without spyware. A conversation ensues about whether Apple should neuter in-app, faux-Safari browsers and further clamp down on the already nigh nonexistent 3rd-party browser ecosystem (another topic of frequent consternation). Another sub-thread raises the alarm that in-app web browsers allow access to *shock*, *awe* <em>The Internet</em>.</p>
<h3 id="a-17-year-old-designed-a-novel-synchronous-reluctance-motor"><a href="#a-17-year-old-designed-a-novel-synchronous-reluctance-motor" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://www.smithsonianmag.com/innovation/this-17-year-old-designed-a-motor-that-could-potentially-transform-the-electric-car-industry-180980550/">A 17-year-old designed a novel synchronous reluctance motor</a></h3><h4 id="august-11-2022-comments-"><a href="#august-11-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 11, 2022 <a href="https://news.ycombinator.com/item?id=32426777">(comments)</a></h4>
<p>A high school student improves upon an electric motor design that doesn’t use rare-earth magnets. Hackernews bitterly resents that they weren’t child prodigies and tries to nitpick the student’s work into meaningless-ness. Other Hackernews conclude that maybe the kids are alright.</p>
<h3 id="arrest-of-suspected-developr-of-tornado-cash"><a href="#arrest-of-suspected-developr-of-tornado-cash" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://www.fiod.nl/arrest-of-suspected-developer-of-tornado-cash/">Arrest of suspected developr of Tornado Cash</a></h3><h4 id="august-12-2022-comments-"><a href="#august-12-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 12, 2022 <a href="https://news.ycombinator.com/item?id=32436413">(comments)</a></h4>
<p>The Dutch Fiscal Information and Investigation Service (business model: “Uber for stopping financial crimes”) arrests a man believed to be the developer of the Ethereum tumbler Tornado Cash (business model: “Uber for committing financial crimes”). Hackernews is very concerned about their future prospects if all of a sudden governments are arresting people who build software designed to let people commit crimes. One subthread devolves into arguments about gun rights in the United States (business model: “Uber for racial inequality”) and others into general fearmongering about the end of privacy as we know it.</p>
<h3 id="i-hacked-my-car"><a href="#i-hacked-my-car" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://programmingwithstyle.com/posts/howihackedmycar/">I hacked my car</a></h3><h4 id="august-13-2022-comments-"><a href="#august-13-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 13, 2022 <a href="https://news.ycombinator.com/item?id=32447650">(comments)</a></h4>
<p>In which the author finds a series of vulnerabilities that should be embarassing for a company with a 36B USD market cap, culminating in finding the private key used to sign their car’s firmware on the internet—an engineer having evidently reused it from a tutorial. At over 3000 words, most Hackernews can’t be bothered with reading it and, as such, the comments are a barren wasteland. Hackernews mostly has complaints about their own cars. Another Hackernews does a casual racism and slights the engineering ability of an entire continent (but it’s definitely okay because he’s personally had bad experiences with <em>those</em> people).</p>
<h3 id="oasis-small-statically-linked-linux-system"><a href="#oasis-small-statically-linked-linux-system" class="header-anchor" aria-hidden="true" role="presentation">###</a> <a href="https://github.com/oasislinux/oasis">Oasis: Small statically-linked Linux system</a></h3><h4 id="august-14-2022-comments-"><a href="#august-14-2022-comments-" class="header-anchor" aria-hidden="true" role="presentation">####</a> August 14, 2022 <a href="https://news.ycombinator.com/item?id=32458744">(comments)</a></h4>
<p>Some developers have come up with a Linux (business model: “Uber for FOSS dweebs”) distribution that will be even more annoying to use than the usual musl-based ones. Half of Hackernews rails against dynamic linking and the other half rails against static linking. Compromise is on no one’s mind; this can only end in war. Only one Hackernews is excited about any other potential merit of the project (namely that it boots a few seconds faster than their current distro of choice).</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Contrary to the title, I will not be doing this weekly for I have neither the time nor the energy. I deeply respect that n-gate was able to do it for nearly 5 years, I couldn’t have managed a fraction as long. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Adopting TextKit 2</title>
      <link>https://shadowfacts.net/2022/textkit-2/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/textkit-2/</guid>
      <pubDate>Sun, 31 Jul 2022 22:31:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>With iOS 16, Apple switched on TextKit 2 for UITextViews. But, if you access any of the TextKit 1 objects on the text view, it will automatically fall back to a compatibility mode. <a href="/2020/uipreviewparameters-textlinerects/" data-link="/2020/uipreviewparameters-textlinerects/">All of the work I did</a> to mimic Safari’s link context menu animation was, of course, using the TextKit 1 APIs, so it was blocking me from fully adopting TextKit 2. So, here’s how to update that code.</p>
<!-- excerpt-end -->
<p>The first part of my implementation that needed to change is how I get which link is being tapped. I have a function called <code>getLinkAtPoint(_:)</code> that takes a CGPoint in the coordinate space of the view and tries to find the link at that point. To update it, almost the entire old body of the function is wrapped in an if statement that checks if TextKit 2 is available:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> getLinkAtPoint(<span class="hl-kw">_</span> point: <span class="hl-type">CGPoint</span>) -&gt; (<span class="hl-type">URL</span>, <span class="hl-type">NSRange</span>)? {
	<span class="hl-kw">let</span> pointInTextContainer = <span class="hl-type">CGPoint</span>(x: point.<span class="hl-prop">x</span> - textContainerInset.<span class="hl-prop">left</span>, y: point.<span class="hl-prop">y</span> - textContainerInset.<span class="hl-prop">top</span>)
	<span class="hl-kw">if #available</span>(iOS <span class="hl-num">16.0</span>, *),
	   <span class="hl-kw">let</span> textLayoutManager = <span class="hl-kw">self</span>.<span class="hl-fn">textLayoutManager</span> {
		<span class="hl-cmt">// ...</span>
	} <span class="hl-kw">else</span> {
		<span class="hl-cmt">// ...</span>
	}
}
</code></pre>
<p>Note that I fall back to the TextKit 1 path if the app’s not running on iOS 16 <em>or</em> the <code>NSTextLayoutManager</code> is not available. Even on iOS 16, a text view may still fall back to TextKit 1 if the old API is used—in which case, the TextKit 2 stack may be swapped out for TextKit 1. In my testing this can happen occasionally even if you’re never using the TextKit 1 API yourself, meaning something in the framework is accessing it (though this may simply be a beta bug).</p>
<p>When TextKit 2 is available, there are several steps we need to go through to get the attributes at a point.</p>
<p>First, we get the text layout fragment that the layout manager has for the point in the coordinate space of the text container. The documentation is sparse, but in my testing, layout fragments correspond to paragraphs.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">guard let</span> fragment = textLayoutManager.<span class="hl-fn">textLayoutFragment</span>(for: pointInTextContainer) <span class="hl-kw">else</span> {
	<span class="hl-kw">return nil</span>
}
</code></pre>
<p>From there, we get the line fragment (corresponding to a visual line of text) that contains our point. To get the line fragment, there is no builtin helper method, so we just go through the layout fragment’s <code>textLineFragments</code> array until we find the one that matches. For each line fragment, we check if its typographic bounds contain the target point converted to the layout fragment’s coordinate space.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> pointInLayoutFragment = <span class="hl-type">CGPoint</span>(x: pointInTextContainer.<span class="hl-prop">x</span> - fragment.<span class="hl-prop">layoutFragmentFrame</span>.<span class="hl-prop">minX</span>, y: pointInTextContainer.<span class="hl-prop">y</span> - fragment.<span class="hl-prop">layoutFragmentFrame</span>.<span class="hl-prop">minY</span>)
<span class="hl-kw">guard let</span> let lineFragment = fragment.<span class="hl-prop">textLineFragments</span>.<span class="hl-fn">first</span>(where: { lineFragment <span class="hl-kw">in</span>
		  lineFragment.<span class="hl-prop">typographicBounds</span>.<span class="hl-fn">contains</span>(pointInLayoutFragment)
	 }) <span class="hl-kw">else</span> {
	<span class="hl-kw">return nil</span>
}
</code></pre>
<p>If there’s no matching layout or line fragment, that means the given location is on a piece of text and therefore there’s no link, so the method returns <code>nil</code>.</p>
<p>After that, we can get the tapped character index by using the <code>characterIndex(for:)</code> method with the target point converted to the line fragment’s coordinate space.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> pointInLineFragment = <span class="hl-type">CGPoint</span>(x: pointInLayoutFragment.<span class="hl-prop">x</span> - lineFragment.<span class="hl-prop">typographicBounds</span>.<span class="hl-prop">minX</span>, y: pointInLayoutFragment.<span class="hl-prop">y</span> - lineFragment.<span class="hl-prop">typographicBounds</span>.<span class="hl-prop">minY</span>)
<span class="hl-kw">let</span> charIndex = lineFragment.<span class="hl-fn">characterIndex</span>(for: pointInLineFragment)
</code></pre>
<p>And then we can use the line fragment’s attributed string to lookup the attribute:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> range = <span class="hl-type">NSRange</span>()
<span class="hl-kw">guard let</span> link = lineFragment.<span class="hl-prop">attributedString</span>.<span class="hl-fn">attribute</span>(.<span class="hl-prop">link</span>, at: charIndex, longestEffectiveRange: &amp;range, in: lineFragment.<span class="hl-prop">attributedString</span>.<span class="hl-prop">fullRange</span>) <span class="hl-kw">as</span>? <span class="hl-type">URL</span> <span class="hl-kw">else</span> {
	<span class="hl-kw">return nil</span>
}

<span class="hl-kw">let</span> textLayoutFragmentStart = textLayoutManager.<span class="hl-fn">offset</span>(from: textLayoutManager.<span class="hl-prop">documentRange</span>.<span class="hl-prop">location</span>, to: fragment.<span class="hl-prop">rangeInElement</span>.<span class="hl-prop">location</span>)
<span class="hl-kw">let</span> rangeInSelf = <span class="hl-type">NSRange</span>(location: range.<span class="hl-prop">location</span> + textLayoutFragmentStart, length: range.<span class="hl-prop">length</span>)
<span class="hl-kw">return</span> (link, rangeInSelf)
</code></pre>
<p>One important thing to note is that the line fragment’s <code>attributedString</code> property is an entirely separate string from the text view’s atttributed string. So the return value of <code>characterIndex</code> and the longest effective range have indices into the <em>substring</em>. The rest of my code expects the return value to be a range in the index-space of the full string, so I need to convert it by adding the offset between the beginning of the document and the beginning of the line fragment’s substring.</p>
<p>For the legacy TextKit 1 path, I use the <code>characterIndex(for:in:fractionOfDistanceBetweenInsertionPoints:)</code> method on the layout manager to get the character index and then look up the attribute at that location. I won’t go into detail in that code here, since it’s more straightforward—and lots of other examples can be found online.</p>
<p>Next up: context menu previews. The vast majority of the code is unchanged, all that needs to be done is changing how we get the rects spanned by a range in the text.</p>
<p>In the <code>contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)</code> method, rather than always using the TextKit 1 API, we again check if TextKit 2 is available, and if so, use that:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> textLineRects = [<span class="hl-type">CGRect</span>]()
<span class="hl-kw">if #available</span>(iOS <span class="hl-num">16.0</span>),
   <span class="hl-kw">let</span> textLayoutManager = <span class="hl-kw">self</span>.<span class="hl-fn">textLayoutManager</span> {
	<span class="hl-kw">let</span> contentManager = textLayoutManager.<span class="hl-prop">contentManager</span>!
	<span class="hl-kw">guard let</span> startLoc = contentManager.<span class="hl-fn">location</span>(contentManager.<span class="hl-prop">documentRange</span>.<span class="hl-prop">location</span>, offsetBy: range.<span class="hl-prop">location</span>),
	      <span class="hl-kw">let</span> endLoc = contentManager.<span class="hl-fn">location</span>(startLoc, offsetBy: range.<span class="hl-prop">length</span>),
		  <span class="hl-kw">let</span> textRange = <span class="hl-type">NSTextRange</span>(location: startLoc, end: endLoc) <span class="hl-kw">else</span> {
		<span class="hl-kw">return nil</span>
	}
	textLayoutManager.<span class="hl-fn">enumerateTextSegments</span>(in: textRange, type: .<span class="hl-prop">standard</span>, options: .<span class="hl-prop">rangeNotRequired</span>) { <span class="hl-kw">_</span>, rect, <span class="hl-kw">_</span>, <span class="hl-kw">_ in</span>
		textLineRects.<span class="hl-fn">append</span>(rect)
		<span class="hl-kw">return true</span>
	}
} <span class="hl-kw">else</span> {
	<span class="hl-kw">let</span> notFoundRange = <span class="hl-type">NSRange</span>(location: <span class="hl-type">NSNotFound</span>, length: <span class="hl-num">0</span>)
	<span class="hl-kw">self</span>.<span class="hl-prop">layoutManager</span>.<span class="hl-fn">enumerateEnclosingRects</span>(forGlyphRange: linkRange,
											   withinSelectedGlyphRange: notFoundRange,
											   in: <span class="hl-kw">self</span>.<span class="hl-prop">textContainer</span>) { (rect, stop) <span class="hl-kw">in</span>
		textLineRects.<span class="hl-fn">append</span>(rect)
	}
}
</code></pre>
<p>In the TextKit 2 path, we get the <code>NSTextContentManager</code> and force-unwrap it (as far as I can tell, this is never <code>nil</code> and is only optional because it’s weak).</p>
<p>We use the content manager to convert the <code>NSRange</code> of the link that’s being previewed into an <code>NSTextRange</code> (a range of <code>NSTextLocation</code>s, which are opaque objects that represent locations in the document however the content manager sees fit). I’m not sure in what circumstances these calls could fail, but if any of them do, we return <code>nil</code> and let the framework use the default preview.</p>
<p>With that, we can call <code>enumerateTextSegments</code> to get the bounding rectangles of the text segments. Since these are in the coordinate space of the text layout manager, there’s nothing further we need to do, so we can just add them to the <code>textLineRects</code> array. And from the block, we return true to continue enumerating. One minor thing to note is that we can pass the <code>.rangeNotRequired</code> option to tell the framework to skip calculating text ranges for every segment since we don’t need them.</p>
<p>From there, the code is exactly the same as last time.</p>
<p>And with those changes in place, I can use my app without any warnings about text views falling back to TextKit 1 and the accompanying visual artifacts.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Clarus Returns Home</title>
      <link>https://shadowfacts.net/2022/clarus/</link>
      <category>misc</category>
      <guid>https://shadowfacts.net/2022/clarus/</guid>
      <pubDate>Tue, 14 Jun 2022 14:11:42 +0000</pubDate>
      <content:encoded><![CDATA[<figure>
	<div style="display: flex; flex-direction: row; align-items: center; background-color: white;">
		<img src="/2022/clarus/clarus-kare.png" alt="Susan Kare's pixel art dogcow icon" style="width: 50%; image-rendering: pixelated;">
		<img src="/2022/clarus/clarus-smooth.png" alt="The high resolution dogcow icon that ships with macOS Ventura" style="width: 50%;">
	</div>
	<figcaption>How it started / How it's going</figcaption>
</figure>
<p>Did you know that with macOS Ventura, Clarus the Dogcow has at long last returned home? Recently, while doing something else, I accidentally hit Cmd+Shift+P which opened the Page Setup dialog. I was greeted, surprisingly, with a new high-resolution version of the classic Clarus icon that I’d never seen before. I looked at it briefly, and then closed the dialog and went back to whatever I was doing before. I had assumed that because I’d been in a 3rd-party app at the time, that the Clarus icon was just some easter egg the developer had left. But a little while later, I got to thinking. What were the chances that someone went to the trouble of customizing the Page Setup dialog, of all things, just for an easter egg? Zero, it turns out. That dialog shows Clarus on the page preview in every app.</p>
<!-- excerpt-end -->
<img src="/2022/clarus/page-setup.png" alt="The Page Setup dialog. The page preview on the left shows the high-resolution Clarus icon.">
<p>I don’t have a Monterey machine to test it at the moment (I, er, <a href="https://social.shadowfacts.net/notice/AKGSrBOxnVDVO0ueem" data-link="social.shadowfacts.net/notice/AKGSrBOxnV…">accidentally</a> updated my laptop to the beta), but I <em>believe</em> this is a new change with Ventura.</p>
<p><strong>Update:</strong> I installed Monterey in a virtual machine to check, and, indeed, the Page Setup dialog there bears no sign of Clarus.</p>
<p>The next step, then—having been thoroughly nerd-sniped by this—was to figure out where the icon was coming from and if I could pull it out of whatever nook it was hidden in.</p>
<p>The first stop was <a href="https://developer.apple.com/documentation/appkit/nspagelayout" data-link="developer.apple.com/documentation/appkit…"><code>NSPageLayout</code></a>, the panel object that is responsible for displaying the panel. It was unlikely that the class would actually contain the implementation of the panel, but it was at least a starting point.</p>
<p>In order to actually look at the disassembled implementation of this AppKit class, I needed the actual AppKit framework binary. Since macOS Big Sur, all system framework binaries are stored merged in the <code>dyld</code> shared cache, rather than in separate files. But, I need them as separate files in order to actually inspect them. </p>
<p>Since the <a href="/2021/scrollswitcher/" data-link="/2021/scrollswitcher/">last time</a> I wrote about this, a couple things have changed. Before, I built the Apple <code>dyld_shared_cache_util</code> from one of the periodic <code>dyld</code> source dumps. This is annoying because you have to make a bunch of changes to the source code to get it to compile outside of an Apple-internal environment. It also may break whenever there’s an OS update. So, I’ve switched to using <a href="https://github.com/keith/dyld-shared-cache-extractor" data-link="github.com/keith/dyld-shared-cache-extra…">this utility</a> which uses the <code>dyld_extractor.bundle</code> that ships with Xcode. The other difference since before is a minor one: the dyld shared cache has moved. Wheras before it was in <code>/System/Library/dyld/</code>, in the Ventura beta it’s moved to <code>/System/Cryptexes/OS/System/Library/dyld/</code> (the Cryptex seems to be part of the <a href="https://threedots.ovh/blog/2022/06/a-quick-look-at-macos-rapid-security-response/" data-link="threedots.ovh/blog/2022/06/a-quick-look-…">Rapid Security Response</a> feature Apple announced).</p>
<p>With the shared cache extracted, I could load the AppKit binary into Hopper (I had to disable the Objective-C analysis, otherwise the app crashed when trying to load the binary) and start poking around. I searched for the <code>NSPageLayout</code> class that I’m interested in, and looked at the <code>runModalWithPrintInfo:</code> method, since that sounded like a good candidate for something that would lead to the bulk of the implementation. And, indeed, it was. The method appears to be a fairly simple wrapper around the <code>PMPrepare...</code> function that sounds like it lives in a separate private framework.</p>
<div class="article-content-wide">
	<img src="/2022/clarus/appkit.png" alt="Hopper window with AppKit showing the runModalWithPrintInfo: method">
</div>
<aside class="inline">
<p>Curious about what those <code>_objc_msgSend$...</code> calls are? I would be to, if I haddn’t watched the fascinating <a href="https://developer.apple.com/wwdc22/110363" data-link="developer.apple.com/wwdc22/110363">Improve app size and runtime performance</a> session from WWDC this year.</p>
</aside>
<p>The next step was figuring out where that prepare function is actually implemented. Running <code>otool -L</code> on the AppKit binary doesn’t reveal anything obviously useful, but in the PrivateFrameworks directory extracted from the dyld shared cache, there’s something called <code>PrintingPrivate.framework</code>, which sounds promising. Opening it up in Hopper, I saw that this is indeed the framework I was looking for.</p>
<div class="article-content-wide">
	<img src="/2022/clarus/printingprivate.png" alt="PrintingPrivate in Hopper showing the _PMPrepareAppKitPageSetupDialogWithPrintInfoPrivate function">
</div>
<p>Looking at the implementation of the prepare function, what immediately jumps out is the call to <code>_LoadAndGetPrintingUIBundle</code>. This seems to be yet another layer of indirection with the actual thing implemented in a different bundle. There’s also a call in the else branch to the similarly-named <code>_LoadAndGetPrintCocoaUIBundle</code>, but let’s start with the first one in hopes that it’s more common.</p>
<p>The implementation of that function goes through another helper function and it ends up loading a <code>PrintingUI.bundle</code> plugin from inside the PrintingPrivate framework bundle. This one isn’t part of the dyld shared cache, so I can just open it right up in Hopper without any fuss.</p>
<p>If you look for the function PrintingPrivate calls, it turns out it winds up in a method on <code>PMPageSetupController</code>. This sounds promising, let’s see what else that class can do.</p>
<p>What’s this? A method called <code>updateClarus</code>? Could it be? Have we finally reached it?</p>
<div class="article-content-wide">
	<img src="/2022/clarus/printingui.png" alt="The PrintingUI binary in Hopper with the search panel showing a bunch of methods with 'clarus' in the name">
</div>
<p>Yes! Clarus, I’m coming! One method that sounds particularly encouraging is <code>-[PMPageSetupController setClarusImageView:]</code>. If I can find out what’s setting the image view, maybe that’ll lead to where it’s being configured with the image.</p>
<p>Unfortunately, the setter for that property isn’t referenced anywhere in the PrintingUI binary. Nor is the getter. I was stuck here for a while, until I realized that the setter not being called anywhere was probably a sign that the UI was defined in a Nib and that an outlet was added from Interface Builder, even though it was never used.</p>
<p>Sure enough, in the plugin bundle’s resources, there is a <code>PMPageSetup.nib</code>. And if the page setup UI is defined in a Nib, and Clarus is being shown in a image view, the image itself is probably located in the asset catalog.</p>
<p>Using the system <code>assetutil</code> program, one can list all of the files in a compiled asset catalog. And sure enough, there she is:</p>
<pre class="highlight" data-lang="shell"><code>$ <span class="hl-fn">assetutil</span> <span class="hl-const">--info</span> /System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/PrintingUI.bundle/Contents/Resources/Assets.car <span class="hl-op">|</span> <span class="hl-fn">grep</span> <span class="hl-const">-i</span> clarus
    "Name" : "Clarus",
    "RenditionName" : "ClarusSmooth2.pdf",
    "Name" : "Clarus",
    "RenditionName" : "ClarusSmooth2.pdf",
    "Name" : "Clarus",
    "RenditionName" : "ClarusSmooth2.pdf",</code></pre>
<p>To actually extract the image from the asset catalog, I needed to use a third-party tool. <a href="https://github.com/bartoszj/acextract" data-link="github.com/bartoszj/acextract">acextract</a> worked perfectly on the first try, though it did need couple of additional <code>@import</code>s to compile on Ventura since <a href="https://twitter.com/illian/status/1534014772848365568" data-link="twitter.com/illian/status/15340147728483…">Foundation no-longer re-exports CoreGraphics</a>.</p>
<aside>
<p>The <code>assetutil</code> manpage does contain a reference to a dump option (<code>-d</code>/<code>--dump</code>) which is curiously not present in the manpage nor is it recognized by the program. Perhaps an Apple-internal feature that is excluded from compilation in public builds?</p>
</aside>
<p>And with that, I finally gazed upon the 512 × 512px beauty that is Smooth Clarus:</p>
<figure>
	<img src="/2022/clarus/clarus-smooth.png" alt="Smooth Clarus">
</figure>
<p>The version shown here I’ve added a white background to, so it’s not invisible in dark mode. The <a href="/2022/clarus/Clarus256x256@2x.png" data-link="/2022/clarus/Clarus256x256@2x.png">original image</a> has a transparent background.</p>
<aside class="inline">
<p>The keen-eyed among you may notice that although, it had a <code>.pdf</code> extension in <code>assetutil</code> info, I’ve given it here as a PNG. I was confused by this too, but upon closer inspection I believe the PNG is what ships with the OS. Although the <code>.car</code> format is not documented, you can still open it up in a hex viewer and learn a bit about what it contains. Looking through it, there appears to be some metadata for each file, followed by the image data itself.</p>
<p>As <code>assetutil</code> showed, there are multiple entries for <code>ClarusSmooth2.pdf</code>—and one of them is followed by data that starts with the PDF file format header (<code>%PDF</code>). But, unfortunately, extracting that data into a separate file seems to result in a blank PDF. And I don’t know enough about the format to figure out whether there is any vector data in it, or if it truly is empty.</p>
<p><strong>Update:</strong> <a href="https://mastodon.social/users/clith/statuses/108482914539340482" data-link="mastodon.social/users/clith/statuses/108…">Reid Ellis</a> managed to extract the actual PDF data, so here’s Clarus in all of the <a href="/2022/clarus/clarus.pdf" data-link="/2022/clarus/clarus.pdf">infinite smoothness</a>.</p>
</aside>
<p>Lastly, if you’re writing a Mac app and would like to hide Clarus somewhere, you can load the bundle yourself and then pull the image out like so:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> bundle = <span class="hl-type">Bundle</span>(path: <span class="hl-str">"/System/Library/PrivateFrameworks/PrintingPrivate.framework/Versions/A/Plugins/PrintingUI.bundle"</span>)!
<span class="hl-kw">try</span>! bundle.<span class="hl-fn">loadAndReturnError</span>()
<span class="hl-kw">let</span> image = bundle.<span class="hl-fn">image</span>(forResource: <span class="hl-str">"Clarus"</span>)
</code></pre>
<p>I’m not sure if the Mac App Store would consider that using private SPI, so use it at your own risk.</p>
<p>It would be very cool to see Clarus return as an SF Symbol some day. If hundreds of icons for various Apple products can go in, so too can everyone’s favorite dogcow.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Part 12: Typed Variables</title>
      <link>https://shadowfacts.net/2022/typed-variables/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2022/typed-variables/</guid>
      <pubDate>Wed, 25 May 2022 20:38:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Hi. It’s been a while. Though the pace of blog posts fell off a cliff last year<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, I’ve continued working on my toy programming language on and off. </p>
<!-- excerpt-end -->
<h2 id="part-1-type-theory-is-for-chumps"><a href="#part-1-type-theory-is-for-chumps" class="header-anchor" aria-hidden="true" role="presentation">##</a> Part 1: Type Theory is for Chumps</h2>
<p>I spent a while thinking about what I wanted the type system to look like—I do want some level of static typing, I know that much—but it got to the point where I was tired of thinking about it and just wanted to get back to writing code. So, lo and behold, the world’s simplest type system:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-attr">#[derive(Debug, PartialEq, Clone, Copy)]</span>
<span class="hl-kw">enum</span> <span class="hl-type">Type</span> {
	Integer,
	Boolean,
	String,
}

<span class="hl-kw">impl</span> <span class="hl-type">Type</span> {
	<span class="hl-kw">fn</span> <span class="hl-fn">is_assignable_to</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>, <span class="hl-var">other</span>: <span class="hl-op">&amp;</span><span class="hl-type">Type</span>) -&gt; <span class="hl-builtin">bool</span> {
		<span class="hl-builtin">self</span> == other
	}
}
</code></pre>
<p>Then, in the <code>Context</code>, rather than variables just being a map of names to <code>Value</code>s, the map now stores <code>VariableDecl</code>s:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">struct</span> <span class="hl-type">VariableDecl</span> {
	<span class="hl-prop">variable_type</span>: <span class="hl-type">Type</span>,
	<span class="hl-prop">value</span>: <span class="hl-type">Value</span>,
}
</code></pre>
<p>So variable declaration and lookup now goes through a simple helper in the function that creates the <code>VariableDecl</code>.</p>
<p>For now, types at variable declarations are optional at parse time since I haven’t touched type inference yet and I didn’t want to go back and update a bunch of unit tests. They are, however, inferred at evaluation time, if one wasn’t specified.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_statement</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Statement</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">let</span> node = <span class="hl-kw">match</span> token {
		<span class="hl-type">Token</span>::Let =&gt; {
			<span class="hl-kw">let</span> name: <span class="hl-type">String</span>;
			<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(<span class="hl-type">Token</span>::Identifier(s)) = it.<span class="hl-fn">peek</span>() {
				name = s.<span class="hl-fn">clone</span>();
				it.<span class="hl-fn">next</span>();
			} <span class="hl-kw">else</span> {
				<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;expected identifier after let&quot;</span>);
			}
			<span class="hl-kw">let</span> <span class="hl-kw">mut</span> variable_type = None;
			<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(<span class="hl-type">Token</span>::Colon) = it.<span class="hl-fn">peek</span>() {
				it.<span class="hl-fn">next</span>();
				variable_type = Some(<span class="hl-fn">parse_type</span>().<span class="hl-fn">expect</span>(<span class="hl-str">&quot;type after colon in variable declaration&quot;</span>));
			}
			<span class="hl-fn">expect_token</span><span class="hl-fn">!</span>(it, Equals, <span class="hl-str">&quot;equals in variable declaration&quot;</span>);
			<span class="hl-kw">let</span> value = <span class="hl-fn">parse_expression</span>(it).<span class="hl-fn">expect</span>(<span class="hl-str">&quot;initial value in variable declaration&quot;</span>);
			Some(Statement::<span class="hl-type">Declare</span> {
				name,
				variable_type,
				value,
			})
		}
		<span class="hl-cmt">// ...</span>
	};
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The <code>parse_type</code> function is super simple, so I won’t go over it—it just converts a the tokens for string/int/bool into their respective <code>Type</code>s. I call <code>expect</code> on the result of that type and then again wrap it in a <code>Some</code>, which seems redundant, because if whatever followed the colon wasn’t a type, there’s a syntax error and I don’t want to continue.</p>
<p>Actually evaluating the variable declaration is still pretty straightforward, though it now checks that the type the initialization expression evaluated to matches the declared type:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval_declare_variable</span>(
    <span class="hl-var">name</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>,
    <span class="hl-var">mutable</span>: <span class="hl-builtin">bool</span>,
    <span class="hl-var">variable_type</span>: <span class="hl-op">&amp;</span><span class="hl-type">Option</span>&lt;<span class="hl-type">Type</span>&gt;,
    <span class="hl-var">value</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>,
    <span class="hl-var">context</span>: <span class="hl-op">&amp;</span><span class="hl-type">ContextRef</span>,
) {
    <span class="hl-kw">let</span> val = <span class="hl-fn">eval_expr</span>(value, context);
    <span class="hl-kw">let</span> variable_type = <span class="hl-kw">match</span> variable_type {
        Some(declared) =&gt; {
            <span class="hl-fn">assert</span><span class="hl-fn">!</span>(
                val.value_type().is_assignable_to(declared),
                <span class="hl-str">&quot;variable value type is not assignable to declared type&quot;</span>
            );
            <span class="hl-op">*</span>declared
        }
        None =&gt; val.<span class="hl-fn">value_type</span>(),
    };
    context
        .<span class="hl-fn">borrow_mut</span>()
        .<span class="hl-fn">declare_variable</span>(name, mutable, variable_type, val);
}
</code></pre><h2 id="part-2-variable-variables"><a href="#part-2-variable-variables" class="header-anchor" aria-hidden="true" role="presentation">##</a> Part 2: Variable Variables</h2>
<p>The other bit I added was mutable variables, so that I could write a small program that did something non-trivial.</p>
<p>To do this, I changed the <code>VariableDecl</code> struct I showed above to hold a <code>ValueStorage</code> rather than a <code>Value</code> directly.</p>
<p><code>ValueStorage</code> is an enum with variants for mutable and immutable variables. Immutables variables simply own their <code>Value</code>. Mutable ones, though, wrap it in a <code>RefCell</code> so that it can be mutated.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">ValueStorage</span> {
	Immutable(<span class="hl-type">Value</span>),
	Mutable(<span class="hl-type">RefCell</span>&lt;<span class="hl-type">Value</span>&gt;),
}
</code></pre>
<p>Setting the value is straightforward, but getting them is a bit annoying because <code>Value</code> isn’t <code>Copy</code>, since it may own a string. So, there are a couple of helper functions: one to access the borrowed value and one to clone it.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">impl</span> <span class="hl-type">ValueStorage</span> {
    <span class="hl-kw">fn</span> <span class="hl-fn">set</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>, <span class="hl-var">value</span>: <span class="hl-type">Value</span>) {
        <span class="hl-kw">match</span> <span class="hl-builtin">self</span> {
            <span class="hl-type">ValueStorage</span>::Immutable(_) =&gt; <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cannot set immutable variable&quot;</span>),
            <span class="hl-type">ValueStorage</span>::Mutable(cell) =&gt; {
                <span class="hl-op">*</span>cell.<span class="hl-fn">borrow_mut</span>() = value;
            }
        }
    }

    <span class="hl-kw">fn</span> <span class="hl-fn">with_value</span>&lt;<span class="hl-type">R</span>, <span class="hl-type">F</span>: <span class="hl-type">FnOnce</span>(<span class="hl-op">&amp;</span><span class="hl-type">Value</span>) -&gt; <span class="hl-type">R</span>&gt;(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>, <span class="hl-var">f</span>: <span class="hl-type">F</span>) -&gt; <span class="hl-type">R</span> {
        <span class="hl-kw">match</span> <span class="hl-builtin">self</span> {
            <span class="hl-type">ValueStorage</span>::Immutable(val) =&gt; <span class="hl-fn">f</span>(<span class="hl-op">&amp;</span>val),
            <span class="hl-type">ValueStorage</span>::Mutable(cell) =&gt; <span class="hl-fn">f</span>(<span class="hl-op">&amp;</span>cell.<span class="hl-fn">borrow</span>()),
        }
    }

    <span class="hl-kw">fn</span> <span class="hl-fn">clone_value</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>) -&gt; <span class="hl-type">Value</span> {
        <span class="hl-builtin">self</span>.<span class="hl-fn">with_value</span>(|v| v.<span class="hl-fn">clone</span>())
    }
}
</code></pre>
<p>This works, but isn’t ideal. At some point, the complex <code>Value</code> types should probably changed to reference-counted so, even if they’re still not copy-able, cloning doesn’t always involve an allocation.</p>
<p>Lexing and parsing I won’t go into detail on, since it’s trivial. There’s a new for <code>var</code> and whether a declaration starts with that or <code>let</code> controls the mutability.</p>
<p>Setting variables isn’t complicated either: when parsing a statement, if there’s an equals sign after an identifier, that turns into a <code>SetVariable</code> which is evaluated simply by calling the aforementioned <code>set</code> function on the <code>ValueStorage</code> for that variable.</p>
<p>And with that, I can write a little fibonacci program:</p>
<pre class="highlight" data-lang="txt"><code>$ cat fib.toy
var a = 0
var b = 1
var i = 0
while (i &lt; 10) {
	print(&quot;iteration: &quot; + toString(i) + &quot;, a: &quot; + toString(a));
	let tmp = a
	a = b
	b = tmp + a
	i = i + 1
}

$ cargo r -- fib.toy
iteration: 0, a: 0
iteration: 1, a: 1
iteration: 2, a: 1
iteration: 3, a: 2
iteration: 4, a: 3
iteration: 5, a: 5
iteration: 6, a: 8
iteration: 7, a: 13
iteration: 8, a: 21
iteration: 9, a: 34
</code></pre>
<p>I also added a small CLI using <a href="https://lib.rs/structopt" data-link="lib.rs/structopt"><code>structopt</code></a> so I didn’t have to keep writing code inside a string in <code>main.rs</code>.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>During and after WWDC21, basically all of my non-work programming energy shifted onto iOS apps, and then never shifted back. I do recognize the irony of resuming mere weeks before WWDC22. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Update: Swift Packages and Frameworks</title>
      <link>https://shadowfacts.net/2022/swift-package-framework-update/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/swift-package-framework-update/</guid>
      <pubDate>Fri, 08 Apr 2022 02:36:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>A while ago I <a href="/2022/swift-package-framework/" data-link="/2022/swift-package-framework/">wrote</a> about some trouble I had getting Xcode to cooperate with my efforts to bring my app file size back under control after adding a new Swift Package dependency. Well, I’m happy to say I finally have: the most recent TestFlight build of Tusker has a 6.7MB install size, down from 25MB.</p>
<p>Ultimately I did take the route of turning my framework into a Swift Package. I revisited it because I noticed in another project that local packages inside the same folder as the main project worked perfectly fine. The only difference I found was that the project where it worked used only an <code>.xcodeproj</code>, whereas Tusker used an <code>.xcworkspace</code>. So, I deleted the (for unrelated reasons, no longer necessary) workspace and found that, after quitting and relaunching Xcode, the local package worked perfectly fine.</p>
<p>I briefly started writing a feedback report, but upon further testing I found that xcworkspaces in general weren’t the problem—a new project and workspace worked fine. So, I gave up trying to reproduce it and assumed there was just something weird about the 3.5 year old workspace.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Asahi Linux</title>
      <link>https://shadowfacts.net/2022/asahi-linux/</link>
      <category>computers</category>
      <guid>https://shadowfacts.net/2022/asahi-linux/</guid>
      <pubDate>Sun, 20 Mar 2022 14:49:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>The <a href="https://asahilinux.org/2022/03/asahi-linux-alpha-release/" data-link="asahilinux.org/2022/03/asahi-linux-alpha…">alpha release</a> of Asahi Linux, a project to run Linux on Apple Silicon computers, came out a couple days ago. And out of a combination of boredom and curiosity, I thought I’d give it a shot.</p>
<!-- excerpt-end -->
<h2 id="installation"><a href="#installation" class="header-anchor" aria-hidden="true" role="presentation">##</a> Installation</h2>
<p>The installation process went very smoothly. The installer utility the Asahi team built functioned perfectly. The one pain point, however, was when shrinking the default partition to make room for the new Linux one, my whole machine locked up for about three minutes. No input was registered and the entire screen stopped updating. The installer does warn you about this beforehand, but it was kind of nerve-wracking nonetheless. After that, the installation went perfectly smoothly, and I’m now running the Asahi Linux Desktop (a modified version of Arch Linux) on my <a href="/2022/m1-max/" data-link="/2022/m1-max/">M1 Max MacBook Pro</a>.</p>
<h2 id="state-of-linux"><a href="#state-of-linux" class="header-anchor" aria-hidden="true" role="presentation">##</a> State of Linux</h2>
<p>Overall, Linux runs quite well natively on the M1 Max. Despite the alpha state, I haven’t (yet) run into any issues the Asahi team didn’t warn about.</p>
<p>So far, almost all of the software I’e tried to install has worked. The sole exception was Rust, as the prebuilt version in the Arch repo uses jemalloc, which does not support the 16K page size of the M1 family. But, building Rust from source<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> got it working fine.</p>
<p>The biggest gap in Asahi right now is the lack of drivers for the chip’s GPU. This means the machine uses software rendering (llvmpipe), which is a great deal slower than proper GPU rendering would be. However, the M1 Max is fast enough for software rendering to be usable for most tasks. Even watching 1080p youtube in Firefox was fine, and glxgears runs at about 700 FPS. Although moving the cursor seems to be a smooth 60 FPS<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>, everything else appears to be capped at 30. This could be a configuration issue, but I don’t know enough to figure it out (and cursory googling doesn’t reveal anything helpful).</p>
<p>Another as-of-yet unsupported feature on Linux is CPU scaling, meaning all the CPU cores run at their full clock speed continuously, consuming all that extra power. I haven’t had very much time to use it on battery, but in spite of this, the battery life seems usable (probably no worse than my old Intel MBP).</p>
<p>This has been a fairly pleasant experience. Though, after I finish writing this post, I’ll be returning to macOS. A good deal of what I do is macOS-specific, not to mention that I enjoy having audio output.</p>
<p>The work of the Asahi team so far is incredibly impressive. What’s more, some of their efforts, such as the m1n1 bootloader, are going towards supporting other operating systems as well as Linux. Maybe I should try <a href="https://marc.info/?l=openbsd-arm&m=164768992119719&w=2" data-link="marc.info">OpenBSD</a> next…</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>While building Rust from source, the laptop got the hottest I’ve ever felt it outside of playing graphically intensive games. The fan also became audible, though still nowhere near the jet-enigne levels of previous Intel laptops. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>The internal display on the MBP is only detected as supporting 60Hz, rather than the full 120 it supports. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Swift Packages and Frameworks</title>
      <link>https://shadowfacts.net/2022/swift-package-framework/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/swift-package-framework/</guid>
      <pubDate>Thu, 24 Feb 2022 01:23:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Tusker is divided up into two main parts: the app target itself and a separate framework which encapsulates everything that deals with the Mastodon API. I recently added a Swift Package to the app for uninteresting reasons. But, because the package is used both by the framework as well as the app itself, this caused a surprising number of problems.</p>
<!-- excerpt-end -->
<p>Adding the package to the app went perfectly well. I added it in Xcode, set the framework and app to depend on it, and then got to building. Everything worked and ran perfectly normally. But, when the time came to publish a new build to TestFlight, the issues started appearing.</p>
<p>Upon uploading, App Store Connect returned an error telling me that the framework for the Swift Package I’d added wasn’t code signed. This was surprising for a couple reasons: first, Swift Packages are generally statically linked (meaning they’re compiled directly into the binary that uses them) rather than shipping as separate, dynamically-linked frameworks. Second, my Xcode project is setup to automatically handle code signing. Why would it be skipping the framework?</p>
<p>The answer is that it wasn’t. The framework for the framework for the package was getting signed perfectly fine. Just not the right one.</p>
<p>It seems having multiple targets that depend on a Swift package causes Xcode to dynamically link it. As with other frameworks, the framework for the package gets built and embedded in the <code>Frameworks/</code> folder of the app that depends on it.</p>
<p>But the app isn’t the only thing that depends on it. The package framework was also getting embedded inside <em>my</em> framework before it was in turn being embedded in the app.</p>
<pre class="highlight" data-lang="txt"><code>Tusker.app
└── Frameworks
   ├── Pachyderm.framework
   │  └── Frameworks
   │      └── WebURL.framework
   └── WebURL.framework
</code></pre>
<p>Xcode was properly signing the app’s frameworks, it was not signing the nested frameworks. Hence the App Store Connect error.</p>
<p>“No problem,” I naively thought, “I’ll just add a Run Script build phase to codesign the nested one myself.”  Yes problem. Turns out App Store Connect entirely rejects nested frameworks, even if they’re correctly signed.</p>
<p>So, I changed the script to entirely delete the nested Frameworks directory (this is fine at runtime because the runpath search paths includes the top-level Frameworks dir), which finally convinced App Store Connect to accept my build. </p>
<pre class="highlight" data-lang="sh"><code><span class="hl-kw">if</span> [ <span class="hl-str">&quot;<span class="hl-emb">$(<span class="hl-fn">ls</span> <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">BUILT_PRODUCTS_DIR</span>/Tusker.app/Frameworks/Pachyderm.framework/Frameworks/&quot;</span>)</span>&quot;</span> -ne <span class="hl-str">&quot;WebURL.framework&quot;</span> ]; <span class="hl-kw">then</span>
	<span class="hl-fn">echo</span> <span class="hl-str">&quot;error: unexpected framework inside Pachyderm, make sure it's embedded directly in the app&quot;</span>
    <span class="hl-fn">exit</span> 1
<span class="hl-kw">fi</span>
<span class="hl-fn">rm</span> <span class="hl-const">-rf</span> <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">BUILT_PRODUCTS_DIR</span>/Tusker.app/Frameworks/Pachyderm.framework/Frameworks/&quot;</span>
</code></pre>
<p>You might think that’s where this story ends, but, sadly, it’s not. I noticed when downloading the new TestFlight build that the app was up to 25 megabytes in size. That might not sound like much, but the previous version was around 5MB and I hadn’t added anything that should have caused a quintupling in size.</p>
<p>Looking at the archive and comparing it to a previous one, it was clear that almost all of the increase was coming from the package framework.</p>
<p>I’d previously tried out this Swift package in a small test app, so I went back to compare its size to the new version of Tusker. The test project was nowhere near as big—just two megabytes.</p>
<p>The sole relevant difference between the two projects, as far as I can tell, is whether the Swift package is linked statically or dynamically. My best guess for the app size difference is that when the package is linked dynamically, dead code elimination can’t happen across module boundaries. Anything declared as <code>public</code>—or used by something <code>public</code>—must be kept, because you don’t know which parts of it the ultimate consumer needs. When linked statically, <code>public</code>-but-unused code can be stripped, which can result in significant size savings if you’re not using the entire API surface of a package.</p>
<h2 id="addendum"><a href="#addendum" class="header-anchor" aria-hidden="true" role="presentation">##</a> Addendum</h2>
<p>I tried two (2) methods for getting Xcode to statically link everything, in hopes of bringing the binary size back down.</p>
<p>The first method was changing my own framework from being, well, a framework to a Swift package. In theory, this should mean that everything gets statically linked into just the app binary. This should be straightforward, but I want the framework/package code to live in the same Git repo alongside the app, as no one else uses it and versioning it separately is a pain. Xcode… does not like this.</p>
<p>You can create a new package in the same repo as an existing project from Xcode no problem. While doing so, you can add it to the existing xcworkspace without objection. But when you try to add the package as a dependency of the app, it just fails silently.</p>
<p>I can click the “Add Local” button in the add package window, I can select the package directory, and click the add button. And then nothing happens. The package doesn’t show up in the “Swift Packages” tab of the project nor under the dependencies of the target to which I added it. So, compilation just fails  because the module is missing.</p>
<p>After abandoning that idea, the other, similarly unsuccessful, tactic I tried was encapsulating all of the usages of the package’s types within my framework in an opaque type and removing the dependency on the package from the app target. This was in the hopes that the package would be statically linked into the framework and have all the unnecessary bits stripped.</p>
<p>That did not work. I don’t know why. There seems to be very little visibility (read: none at all) into how Xcode chooses to static versus dynamic linking for Swift packages.</p>
<p>That’s where I gave up, so if you have any better ideas, please let me know. At the end of the day, I don’t have the energy to spend more time fighting Xcode over 20 megabytes. Oh well. I should probably throw a report into the void that is Feedback Assistant.</p>
<p><strong>Update:</strong> As of April 2022, I’ve <a href="/2022/swift-package-framework-update/" data-link="/2022/swift-package-framework-update/">resolved</a> this issue.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Re-Fixing WKWebView Scroll Indicators</title>
      <link>https://shadowfacts.net/2022/wkwebview-scroll-indicators-again/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/wkwebview-scroll-indicators-again/</guid>
      <pubDate>Mon, 31 Jan 2022 01:23:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>As my luck would have it, just a few weeks after I published my <a href="/2022/wkwebview-scroll-indicators/" data-link="/2022/wkwebview-scroll-indicators/">last post</a> on this topic, the iOS 15.4 beta came out which broke that hack and once again made my scroll indicators invisible in dark mode.</p>
<!-- excerpt-end -->
<p>Some time ago, a bug was filed against WebKit because setting <code>scrollIndicatorStyle</code> on a web view’s scroll view was broken on iOS 15. The fix for this bug landed in iOS 15.4 and it subtly changed the behavior of WKScrollView when it comes to the indicator style.</p>
<p>The bug was fixed by tracking whether the web view client has overriden the scroll indicator style and, if so, blocking the web view from resetting it internally. Unfortunately, it <a href="https://github.com/WebKit/WebKit/blob/1dbd34cf01d8b5aedcb8820b13cb6553ed60e8ed/Source/WebKit/UIProcess/ios/WKScrollView.mm#L247" data-link="github.com/WebKit/WebKit/blob/1dbd34cf01…">does this</a> by checking if the new indicator style is not <code>.default</code>. So, even if you set it to <code>.default</code> to make it automatically switch based on system appearance, the scroll view will interpret that to mean the indicator style hasn’t been overriden and continue erroneously setting it based on background color (or, in my case, the non-opaqueness of the web view).</p>
<p>The solution is simple, if annoying. You need to check the current user interface style and select the appropriate scroll indicator style yourself.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">override func</span> viewWillAppear(<span class="hl-kw">_</span> animated: <span class="hl-type">Bool</span>) {
	<span class="hl-kw">super</span>.<span class="hl-fn">viewWillAppear</span>(animated)
	<span class="hl-fn">updateScrollIndicatorStyle</span>()
}
<span class="hl-kw">override func</span> traitCollectionDidChange(<span class="hl-kw">_</span> previousTraitCollection: <span class="hl-type">UITraitCollection</span>?) {
	<span class="hl-kw">super</span>.<span class="hl-fn">traitCollectionDidChange</span>(previousTraitCollection)
	<span class="hl-fn">updateScrollIndicatorStyle</span>()
}
<span class="hl-kw">private func</span> updateScrollIndicatorStyle() {
	<span class="hl-kw">guard #available</span>(iOS <span class="hl-num">15.4</span>, *) <span class="hl-kw">else</span> {
		<span class="hl-cmt">// different workaround pre-iOS 15.4</span>
		<span class="hl-kw">return</span>
	}
	<span class="hl-kw">if</span> traitCollection.<span class="hl-prop">userInterfaceStyle</span> == .<span class="hl-prop">dark</span> {
		webView.<span class="hl-prop">scrollView</span>.<span class="hl-prop">indicatorStyle</span> = .<span class="hl-prop">white</span>
	} <span class="hl-kw">else</span> {
		webView.<span class="hl-prop">scrollView</span>.<span class="hl-prop">indicatorStyle</span> = .<span class="hl-prop">black</span>
	}
}
</code></pre>
<p>And, if you, like me were previously using the old, swizzling workaround, you need to disable in on iOS 15.4. If the old workaround remains active, studiously setting the indicator style to <code>.default</code> whenever WebKit would override it, it would merely undo all of our hard work.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Using lol-html (or any Rust crate) in Swift</title>
      <link>https://shadowfacts.net/2022/swift-rust/</link>
      <category>swift</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2022/swift-rust/</guid>
      <pubDate>Thu, 20 Jan 2022 16:44:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I recently started building a new iOS app and found myself with a need to parse HTML in order to extract some information. My goto tool for this in the past has been <a href="https://github.com/scinfu/SwiftSoup" data-link="github.com/scinfu/SwiftSoup">SwiftSoup</a>. In this app, I have to deal with larger documents than I’d used it for previously, and unfortunately, its performance leavse something to be desired. Much of the issue comes from the fact that I only want to extract the first paragraph of a document, but SwiftSoup always needs to parse the entire thing—for large documents, potentially a lot of unnecessary work<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. And, as far as I could find, there are no streaming HTML parsers written in Swift. One I did find, however, was CloudFlare’s <a href="https://github.com/cloudflare/lol-html" data-link="github.com/cloudflare/lol-html">lol-html</a>. It’s specifically designed for speed and low latency, exactly what I want. But it’s written in Rust.</p>
<!-- excerpt-end -->
<p>Getting a Rust library compiled into a form that it could be used from Swift didn’t turn out to be as complicated as I expected, but both Apple Silicon and Mac Catalyst introduced <del>fun</del> wrinkles.</p>
<p>This <a href="https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-06-rust-on-ios.html" data-link="mozilla.github.io/firefox-browser-archit…">blog post</a> from Mozilla was helpful in getting started, but things have changed somewhat in the years since it was written.</p>
<p>The first thing you need to do is install the appropriate targets for the Rust toolchain to let it build targeting iOS devices.</p>
<p><code>aarch64-apple-ios</code> works for all actual devices. Additionally, to build for the iOS Simulator, you also need the <code>aarch64-apple-ios-sim</code> target (if you’re on Apple Silicon) or <code>x86_64-apple-ios</code> (for Intel Macs).</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">rustup</span></span> target add aarch64-apple-ios aarch64-apple-ios-sim x86_64-apple-ios
</code></pre>
<p>To build a Rust project, you need a library crate with the <code>crate-type</code> set to <code>staticlib</code> so that it can be statically linked by the iOS app. The Rust library also needs an API that’s callable from C, i.e. using <code>#[no_mangle]</code> and <code>extern &quot;C&quot;</code> (outside the scope of this post). Fortunately for me, lol-html already includes such an API.</p>
<p>Building for iOS is done by running <code>cargo build</code> with the appropriate <code>--target</code> option. For example:</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> build <span class="hl-const">--release</span> <span class="hl-const">--target</span> aarch64-apple-ios-sim
</code></pre>
<p>With the Rust library built, the next step is configuring Xcode to use it. In the app target’s build settings, the Header Search Paths needs to be updated to include the path to the C headers that correspond to the C-API that the Rust library exposes. In my case, that’s <code>lol-html/c-api/include/</code>.</p>
<p>That’ll get it working if you want to call it from C or Objective-C code in your project. To make it accessible from Swift, you need to add a bridging header that imports whatever library headers you need. For lol-html, there’s only <code>lol_html.h</code>. This will make Rust library’s functions directly accessible from all of the app target’s Swift files.</p>
<p>This is enough to compile it successfully, but to actually link the output into a runnable app, the we need to tell the linker where to find the library.</p>
<p>With a normal library, you could add the static library .a to the Xcode target’s “Frameworks, Libraries, and Embedded Content”. But, because Cargo puts the build products in separate directories depending on which platform it targets (e.g., <code>target/aarch64-apple-ios/release/liblolhtml.a</code>), we need to do it slightly differently. Just adding one of the liblolhtml.a’s to the Xcode target would make the linker try to always link against that specific one regardless of which platform the iOS app is building for. Instead, I modified the “Other Linker Flags” build setting to include <code>-llolhtml</code> and then update the “Library Search Paths” settings on a per-platform basis to tell it 1) that it needs to link against something called <code>liblolhtml.a</code> and 2) where exactly that file can be found.</p>
<p>Configuring the Library Search Paths build setting is kind of annoying, because the Xcode UI doesn’t fully match what the <code>.pbxproj</code> file can actually describe. Clicking the plus button next to a build setting in Xcode lets you pick for which SDKs the setting value applies. But we also need to narrow that down to specific architectures, because the Intel and Apple Silicon simulator builds need different versions of the library.</p>
<p>The easiest way I’ve found to do this is to go into the Build Settings tab of the Xcode target, find Library Search Paths, expand it, and click the little plus button next to each of Debug and Release. (If you click on the “Any Architecture | Any SDK” dropdown, you’ll see what I mean about not being able to actually specify the architecture from the UI.)</p>
<img src="<%= metadata.permalink %>/search-paths-empty.png" alt="The Library Search Paths setting in Xcode showing empty values under Debug and Release">
<p>Then, open the <code>project.pbxproj</code> file in a text editor. I recommend closing the Xcode proejct before making any changes to this file. Search for the newly added line starting with <code>&quot;LIBRARY_SEARCH_PATHS[arch=*]&quot;</code> and replace it with the following. There will be two occurrences of that line (for the debug and releaes configurations) and both need to be replaced.</p>
<pre class="highlight" data-lang="txt"><code>&quot;LIBRARY_SEARCH_PATHS[sdk=iphoneos*]&quot; = &quot;$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios/release/&quot;;
&quot;LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=arm64]&quot; = &quot;$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-sim/release/&quot;;
&quot;LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*][arch=x86_64]&quot; = &quot;$(PROJECT_DIR)/lol-html/c-api/target/x86_64-apple-ios/release/&quot;;
</code></pre>
<p>You’ll need to substitute the <code>lol-html/c-api</code> part for the actual path to the library you’re using. This will tell Xcode to to use the <code>aarch64-apple-ios</code> version for all actual iOS device targets, and the appropriate simulator version depending on the architecture.</p>
<p>After that, you should be able to re-open the project in Xcode and see all the configurations you added in Build Settings.</p>
<img src="<%= metadata.permalink %>/search-paths-ios.png" alt="The Library Search Paths setting in Xcode showing values for any iOS SDK, and for arm64 and x86_64 variants of the simulator SDK">
<p>With that, you should be able to use the Rust library from your Swift code and successfully build and run your app in both the simulator and on a real device.</p>
<h2 id="mac-catalyst"><a href="#mac-catalyst" class="header-anchor" aria-hidden="true" role="presentation">##</a> Mac Catalyst</h2>
<p>My first attempt at getting Catalyst builds to work was just by using the normal Mac targets for the Rust library (e.g., <code>aarch64-apple-darwin</code>). But that results in a link error when Xcode builds the app, because it considers binaries built for Catalyst to be distinct targets from regular macOS.</p>
<p>The separate Rust targets for Catalyst are <code>aarch64-apple-ios-macabi</code> and <code>x86_64-apple-ios-macabi</code> for ARM and Intel respectively. As of writing, these are <a href="https://doc.rust-lang.org/nightly/rustc/platform-support.html#tier-3" data-link="doc.rust-lang.org/nightly/rustc/platform…">tier 3 targets</a>, which means the Rust project doesn’t provide official builds. This, in turn, means to use them you have to build the standard library from source yourself.</p>
<p>Doing so requires a Rust Nightly feature, <a href="https://doc.rust-lang.org/cargo/reference/unstable.html#build-std" data-link="doc.rust-lang.org/cargo/reference/unstab…">build-std</a>, to let Cargo include the standard library in the crate graph for compilation. So, with Nightly installed (<code>rustup toolchain install nightly</code>) and the std source downloaded (<code>rustup component add rust-src --toolchain-nightly</code>), you can run the following command to build for a specific target with the standard library built from source:</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> +nightly build <span class="hl-const">-Z</span> build-std=std,panic_abort <span class="hl-const">--release</span> <span class="hl-const">--target</span> aarch64-apple-ios-macabi
</code></pre>
<p>This separate set of platform/arch combinations requires another set of additions to the Xcode project file, in the sample place as before:</p>
<pre class="highlight" data-lang="txt"><code>&quot;LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=arm64]&quot; = &quot;$(PROJECT_DIR)/lol-html/c-api/target/aarch64-apple-ios-macabi/release&quot;;
&quot;LIBRARY_SEARCH_PATHS[sdk=macosx*][arch=x86_64]&quot; = &quot;$(PROJECT_DIR)/lol-html/c-api/target/x86_64-apple-ios-macabi/release&quot;;
</code></pre>
<p>With that added, the build setting should have values configured for iOS, ARM and Intel Simulators, and ARM and Intel Catalyst:</p>
<img src="<%= metadata.permalink %>/search-paths-catalyst.png" alt="The Library Search Paths setting in Xcode showing values for all platform and architecture combinations">
<p>I initially thought handling universal builds (i.e., combining arm64 and x86_64 into one binary) of the Catalyst app would be complicated, like I would have to lipo them together myself, but it turned out to be entirely painless. Just having the built static libraries for both architectures present in their expected locations is enough. Xcode’s build process takes care of linking each architecture of the app with the respective version of the Rust library and then combining those into one universal package.</p>
<h2 id="build-script"><a href="#build-script" class="header-anchor" aria-hidden="true" role="presentation">##</a> Build Script</h2>
<p>Keeping track of all of those Rust build targets and making sure to rebuild the right ones if anything changes is rather annoying, so I wrote a little script for Xcode to run to take care of it.</p>
<p>It uses the environment variables provided by Xcode to figure out which platform and architecture(s) are being targeted and build the appropriate Rust targets.</p>
<pre class="highlight" data-lang="bash"><code><span class="hl-fn">pushd</span> <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">PROJECT_DIR</span>/lol-html/c-api/&quot;</span>

<span class="hl-fn">build</span>() {
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;Building lol-html for target: <span class="hl-op">$</span><span class="hl-prop">1</span>&quot;</span>

    <span class="hl-fn">~/.cargo/bin/cargo</span> build <span class="hl-const">--release</span> <span class="hl-const">--target</span> <span class="hl-op">$</span><span class="hl-prop">1</span>
}

<span class="hl-fn">build_std</span>() {
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;Building lol-html with std for target: <span class="hl-op">$</span><span class="hl-prop">1</span>&quot;</span>
    
    <span class="hl-fn">~/.cargo/bin/cargo</span> +nightly build <span class="hl-const">-Z</span> build-std=panic_abort,std <span class="hl-const">--release</span> <span class="hl-const">--target</span> <span class="hl-op">$</span><span class="hl-prop">1</span>
}

<span class="hl-kw">if</span> [ <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">PLATFORM_NAME</span>&quot;</span> == <span class="hl-str">&quot;iphonesimulator&quot;</span> ]; <span class="hl-kw">then</span>
    <span class="hl-kw">if</span> [ <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">ARCHS</span>&quot;</span> == <span class="hl-str">&quot;arm64&quot;</span> ]; <span class="hl-kw">then</span>
        <span class="hl-fn">build</span> <span class="hl-str">&quot;aarch64-apple-ios-sim&quot;</span>
    <span class="hl-kw">elif</span> [ <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">ARCHS</span>&quot;</span> == <span class="hl-str">&quot;x86_64&quot;</span> ]; <span class="hl-kw">then</span>
        <span class="hl-fn">build</span> <span class="hl-str">&quot;x86_64-apple-ios&quot;</span>
    <span class="hl-kw">else</span>
        <span class="hl-fn">echo</span> <span class="hl-str">&quot;error: unknown value for \$ARCHS&quot;</span>
        <span class="hl-fn">exit</span> 1
    <span class="hl-kw">fi</span>
<span class="hl-kw">elif</span> [ <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">PLATFORM_NAME</span>&quot;</span> == <span class="hl-str">&quot;iphoneos&quot;</span> ]; <span class="hl-kw">then</span>
    <span class="hl-fn">build</span> <span class="hl-str">&quot;aarch64-apple-ios&quot;</span>
<span class="hl-kw">elif</span> [ <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">PLATFORM_NAME</span>&quot;</span> == <span class="hl-str">&quot;macosx&quot;</span> ]; <span class="hl-kw">then</span>
    <span class="hl-kw">if</span> <span class="hl-fn">grep</span> <span class="hl-const">-q</span> <span class="hl-str">&quot;arm64&quot;</span> &lt;&lt;&lt; <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">ARCHS</span>&quot;</span>; <span class="hl-kw">then</span>
        <span class="hl-fn">build_std</span> <span class="hl-str">&quot;aarch64-apple-ios-macabi&quot;</span>
    <span class="hl-kw">fi</span>
    <span class="hl-kw">if</span> <span class="hl-fn">grep</span> <span class="hl-const">-q</span> <span class="hl-str">&quot;x86_64&quot;</span> &lt;&lt;&lt; <span class="hl-str">&quot;<span class="hl-op">$</span><span class="hl-prop">ARCHS</span>&quot;</span>; <span class="hl-kw">then</span>
        <span class="hl-fn">build_std</span> <span class="hl-str">&quot;x86_64-apple-ios-macabi&quot;</span>
    <span class="hl-kw">fi</span>
<span class="hl-kw">else</span>
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;error: unknown value for \$PLATFORM_NAME&quot;</span>
    <span class="hl-fn">exit</span> 1
<span class="hl-kw">fi</span>
</code></pre>
<p>One thing to note is that when building the universal Mac target, <code>$ARCHS</code> has the value <code>arm64 x86_64</code>. So I check whether the string contains the target architecture, rather than strictly equals, and don’t use elif in the Mac branch so that both architectures are built.</p>
<p>I have it configured to not bother with any of the dependency analysis stuff, because Cargo takes care of only actually rebuilding if something’s changed and when nothing has, the time it takes is negligible so running on every incremental build is fine.</p>
<p>With the script added to Build Phases in Xcode (for some reason it needs to come not just before Link Binary with Libraries but also before Compile Sources), I can run <code>cargo clean</code> in the Rust project directory and then seamlessly build and run from Xcode.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I <a href="https://git.shadowfacts.net/shadowfacts/frenzy-ios/src/commit/e242510c5e601ec309acc4ab5a53972f4a2878cd/ReaderTests/ReaderTests.swift#L27-L53" data-link="git.shadowfacts.net/shadowfacts/frenzy-i…">benchmarked</a> it, and for an average-length document, using lol-html to extract the first paragraph winds up being two orders of magnitude faster than SwiftSoup. And that dramatic difference only increases for longer documents. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Run LWJGL 2 Natively on Apple Silicon</title>
      <link>https://shadowfacts.net/2022/lwjgl-arm64/</link>
      <category>minecraft</category>
      <guid>https://shadowfacts.net/2022/lwjgl-arm64/</guid>
      <pubDate>Mon, 17 Jan 2022 14:28:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Running Minecraft 1.13 and later natively on Apple Silicon Macs isn’t terribly complicated. Since those versions use LWJGL 3 which includes arm64 macOS as a supported platform, you can just <a href="https://www.lwjgl.org/customize" data-link="lwjgl.org/customize">download</a> the appropriate version, replace the LWJGL version info in MultiMC, and you’re off to the races. The end of maintenance for LWJGL 2, however, long predates the ARM transition and so getting Minecraft versions prior to 1.13 up and running requries a bit more work.</p>
<!-- excerpt-end -->
<p>First off: This article assumes you’re using MultiMC and already have the ARM <a href="https://www.azul.com/downloads/?version=java-8-lts&os=macos&architecture=arm-64-bit&package=jdk" data-link="azul.com/downloads/">Zulu 8 JDK</a> installed<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>.</p>
<p>There are guides on the internet that tell you to download some precompiled libraries and run Minecraft through a wrapper script, but doing that doesn’t sit right with me. Didn’t your mother ever tell you not to download executables from strangers on the internet<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>? So, I wanted to actually compile LWJGL 2 and its natives from source myself.</p>
<p>You can find the source code for my modified version of LWJGL 2 <a href="https://github.com/shadowfacts/lwjgl2-arm64" data-link="github.com/shadowfacts/lwjgl2-arm64">here</a> (and you can <a href="https://github.com/LWJGL/lwjgl/compare/master...shadowfacts:master" data-link="github.com/LWJGL/lwjgl/compare/master...…">compare</a> against upstream to see I haven’t made any malicious changes). </p>
<p>If you’re interested, here’s a brief overview of the changes that were necessary. If not, <a href="#building-everything" data-link="#building-everything">skip ahead</a>.</p>
<p>First, there are a bunch of changes to the Ant <code>build.xml</code>. <code>javah</code> was replaced with <code>javac -h</code>, source/target versions less than 1.7 aren’t supported when compiling with a Java 17 JDK. In the <code>build.xml</code> for the native library, the big changes are the macOS SDK being in a different place and that we’re compiling against the Zulu JVM, not the Apple one.</p>
<p>In <code>MacOSXSysImplementation.java</code>, a since-removed class was being used to ask the OS to open URLs. That’s been replaced with a JNI function (see <code>org_lwjgl_MacOSXSysImplementation.m</code>).</p>
<p>In <code>MemoryUtilSun.java</code>, a accessor implementation that was using a removed Sun-internal class was removed (it was only used as a fallback, so it wasn’t replaced with anything).</p>
<p>Some applet code that was using a removed Java archive format was commented out (irrelevant because the applet build is disabled altogether).</p>
<p>Lastly in Java-land, there were a bunch of casts from <code>java.nio.ByteBuffer</code> to <code>Buffer</code> added in places where methods ByteBuffer inherits from Buffer (e.g., <code>flip</code>) are being called. This is because, in between Java 8 and 17, ByteBuffer itself got overriden implementations and so trying to use a LWJGL jar built against Java 17 on Java 8 would result in a <code>NoSuchMethodError</code>.</p>
<p>In the native code, there are a few more changes.</p>
<p>First, an <code>JAWT_MacOSXDrawingSurfaceInfo</code> struct is defined. It’s used for the NSView-backed drawing mode that Minecraft uses on Mac. This was previously defined in the JDK headers, however it’s no longer present. An <a href="https://github.com/phracker/MacOSX-SDKs/blob/master/MacOSX10.7.sdk/System/Library/Frameworks/JavaVM.framework/Versions/A/Headers/jawt_md.h#L42" data-link="github.com/phracker/MacOSX-SDKs/blob/mas…">old version</a> of the Apple JVM headers say that it is being removed, however it is clearly still implemented internally (fortuntely for us).</p>
<p>The other significant change is a bunch of AppKit calls being wrapped in <code>dispatch_sync</code> blocks since they were previously being called from background threads, which AppKit Does Not Like. This solution is rather less than ideal. I suspect <code>dispatch_sync</code> is part of the <a href="https://social.shadowfacts.net/notice/AF0rlm8MrA8fSo1Qa8" data-link="social.shadowfacts.net/notice/AF0rlm8MrA…">significant performance delta</a> between LWJGL3 versions and earlier ones, because it blocks the render thread until the work on the main thread completes. I tried using the <code>-XstartOnFirstThread</code> JVM argument so that the Minecraft client thread would be the same as the AppKit main thread, however that only caused more problems. </p>
<p>Finally, high DPI mode is disabled altogether. I spent some time trying to get it working, but whenever I’d launch the game it would only render at 50% scale. And frankly, I don’t care enough about high DPI mode to spend even more time debugging it.</p>
<p>To get Minecraft up and running, there’s actually a second library that we also need to compile for arm64: jinput. My fork’s source code can be found <a href="https://github.com/shadowfacts/jinput-arm64" data-link="github.com/shadowfacts/jinput-arm64">here</a> (<a href="https://github.com/jinput/jinput/compare/master...shadowfacts:master" data-link="github.com/jinput/jinput/compare/master.…">compare against upstream</a>).</p>
<p>The changes for this one are much simpler, thankfully. A bunch of stuff that we don’t need is disabled in the various maven files.</p>
<p>Then, one (1) unused import for a no-longer-extant Java stdlib class was removed.</p>
<p>Lastly, the <code>build.xml</code> for the macOS platform specific module was changed similar to the LWJGL one to make it use the macOS SDK in the correct location and link agains the Zulu 8 JDK rather than the system JavaVM framework.</p>
<h2 id="building-everything"><a href="#building-everything" class="header-anchor" aria-hidden="true" role="presentation">##</a> Building Everything</h2>
<p>You’ll need <a href="https://ant.apache.org/" data-link="ant.apache.org">Ant</a> and <a href="https://maven.apache.org/" data-link="maven.apache.org">Maven</a> installed if you don’t have them already. You also need to have a current version of Xcode downloaded.</p>
<h3 id="lwjgl"><a href="#lwjgl" class="header-anchor" aria-hidden="true" role="presentation">###</a> LWJGL</h3>
<ol>
<li>Clone the repo: <code>git clone https://github.com/shadowfacts/lwjgl-arm64.git</code></li>
<li>Run <code>ant generate-all</code> to generate some required Java files from templates.</li>
<li>Run <code>ant jars</code> to build the LWJGL jar</li>
<li>Run <code>ant compile_native</code> to build the native libraries</li>
<li>In the <code>libs/macosx/</code> folder inside your LWJGL clone, select <code>liblwjgl.dylib</code> and <code>openal.dylib</code> in Finder and right-click and select Compress. Then rename the created <code>Archive.zip</code> to <code>lwjgl-platform-natives-osx.jar</code></li>
</ol>
<h3 id="jinput"><a href="#jinput" class="header-anchor" aria-hidden="true" role="presentation">###</a> jinput</h3>
<ol>
<li>Clone the repo: <code>git clone https://github.com/shadowfacts/jinput-arm64.git</code></li>
<li>Run <code>mvn package</code> to build everything</li>
</ol>
<h2 id="setup-multi-mc"><a href="#setup-multi-mc" class="header-anchor" aria-hidden="true" role="presentation">##</a> Setup MultiMC</h2>
<p>In MultiMC, create an instance of whichever pre-LWJGL3 version you want and make sure it’s configured to use the ARM Java 8 you installed. Then, in the Version section of the Edit Instance window, click the Open Libraries button in the sidebar. To the folder that opens, copy the <code>lwjgl-platform-natives-osx.jar</code> you created earlier. Then, copy <code>osx-plugin-2.0.10-SNAPSHOT-natives-osx.jar</code> from inside the <code>plugins/OSX/target</code> folder of the jinput directory as well.</p>
<p>Next, in the Version section of the Edit Instance window, select LWJGL 2 and click the Customize and then Edit buttons in the sidebar.</p>
<p>The file that opens needs to be modified to point to local versions of the libraries we compiled. Replace its contents with the JSON below.</p>
<details>
<summary>Click me to expand for full LWJGL 2 version JSON.</summary>
<pre class="highlight" data-lang="json"><code>{
    <span class="hl-key">&quot;formatVersion&quot;</span>: <span class="hl-num">1</span>,
    <span class="hl-key">&quot;libraries&quot;</span>: [
        {
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;net.java.jinput:jinput-platform:2.0.10-SNAPSHOT&quot;</span>,
            <span class="hl-key">&quot;natives&quot;</span>: {
                <span class="hl-key">&quot;linux&quot;</span>: <span class="hl-str">&quot;natives-linux&quot;</span>,
                <span class="hl-key">&quot;osx&quot;</span>: <span class="hl-str">&quot;natives-osx&quot;</span>,
                <span class="hl-key">&quot;windows&quot;</span>: <span class="hl-str">&quot;natives-windows&quot;</span>
            },
            <span class="hl-key">&quot;MMC-hint&quot;</span>: <span class="hl-str">&quot;local&quot;</span>,
			<span class="hl-key">&quot;MMC-filename&quot;</span>: <span class="hl-str">&quot;osx-plugin-2.0.10-SNAPSHOT-natives-osx.jar&quot;</span>
        },
        {
            <span class="hl-key">&quot;downloads&quot;</span>: {
                <span class="hl-key">&quot;artifact&quot;</span>: {
                    <span class="hl-key">&quot;sha1&quot;</span>: <span class="hl-str">&quot;39c7796b469a600f72380316f6b1f11db6c2c7c4&quot;</span>,
                    <span class="hl-key">&quot;size&quot;</span>: <span class="hl-num">208338</span>,
                    <span class="hl-key">&quot;url&quot;</span>: <span class="hl-str">&quot;https://libraries.minecraft.net/net/java/jinput/jinput/2.0.5/jinput-2.0.5.jar&quot;</span>
                }
            },
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;net.java.jinput:jinput:2.0.5&quot;</span>
        },
        {
            <span class="hl-key">&quot;downloads&quot;</span>: {
                <span class="hl-key">&quot;artifact&quot;</span>: {
                    <span class="hl-key">&quot;sha1&quot;</span>: <span class="hl-str">&quot;e12fe1fda814bd348c1579329c86943d2cd3c6a6&quot;</span>,
                    <span class="hl-key">&quot;size&quot;</span>: <span class="hl-num">7508</span>,
                    <span class="hl-key">&quot;url&quot;</span>: <span class="hl-str">&quot;https://libraries.minecraft.net/net/java/jutils/jutils/1.0.0/jutils-1.0.0.jar&quot;</span>
                }
            },
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;net.java.jutils:jutils:1.0.0&quot;</span>
        },
        {
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;org.lwjgl.lwjgl:lwjgl-platform:2.9.4-nightly-20150209&quot;</span>,
            <span class="hl-key">&quot;natives&quot;</span>: {
                <span class="hl-key">&quot;linux&quot;</span>: <span class="hl-str">&quot;natives-linux&quot;</span>,
                <span class="hl-key">&quot;osx&quot;</span>: <span class="hl-str">&quot;natives-osx&quot;</span>,
                <span class="hl-key">&quot;windows&quot;</span>: <span class="hl-str">&quot;natives-windows&quot;</span>
            },
            <span class="hl-key">&quot;MMC-hint&quot;</span>: <span class="hl-str">&quot;local&quot;</span>,
			<span class="hl-key">&quot;MMC-filename&quot;</span>: <span class="hl-str">&quot;lwjgl-platform-natives-osx.jar&quot;</span>
        },
        {
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209&quot;</span>,
            <span class="hl-key">&quot;MMC-hint&quot;</span>: <span class="hl-str">&quot;local&quot;</span>,
            <span class="hl-key">&quot;MMC-filename&quot;</span>: <span class="hl-str">&quot;lwjgl.jar&quot;</span>
        },
        {
            <span class="hl-key">&quot;downloads&quot;</span>: {
                <span class="hl-key">&quot;artifact&quot;</span>: {
                    <span class="hl-key">&quot;sha1&quot;</span>: <span class="hl-str">&quot;d51a7c040a721d13efdfbd34f8b257b2df882ad0&quot;</span>,
                    <span class="hl-key">&quot;size&quot;</span>: <span class="hl-num">173887</span>,
                    <span class="hl-key">&quot;url&quot;</span>: <span class="hl-str">&quot;https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl_util/2.9.4-nightly-20150209/lwjgl_util-2.9.4-nightly-20150209.jar&quot;</span>
                }
            },
            <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;org.lwjgl.lwjgl:lwjgl_util:2.9.4-nightly-20150209&quot;</span>
        }
    ],
    <span class="hl-key">&quot;name&quot;</span>: <span class="hl-str">&quot;LWJGL 2&quot;</span>,
    <span class="hl-key">&quot;releaseTime&quot;</span>: <span class="hl-str">&quot;2017-04-05T13:58:01+00:00&quot;</span>,
    <span class="hl-key">&quot;type&quot;</span>: <span class="hl-str">&quot;release&quot;</span>,
    <span class="hl-key">&quot;uid&quot;</span>: <span class="hl-str">&quot;org.lwjgl&quot;</span>,
    <span class="hl-key">&quot;version&quot;</span>: <span class="hl-str">&quot;2.9.4-nightly-20150209&quot;</span>,
    <span class="hl-key">&quot;volatile&quot;</span>: <span class="hl-const">true</span>
}
</code></pre></details>
<p>From there, you should be able to launch Minecraft natively on ARM:</p>
<img src="/2022/lwjgl-arm64/minecraft.png" alt="Minecraft 1.7.10 running natively on ARM">
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>If you want to play modded, a Java 8 runtime is necessary even if you’ve already got a newer JRE installed since older versions of FML are not compatible with the Project Jigsaw changes introduced in Java 9, even though Minecraft itself is. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Yes, playing with mods is doing just that. However, I feel there’s a big difference between downloading mods that are part of widely played modpacks and downloading native binaries from completely random people. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Fixing Scroll Indicators in Non-Opaque WKWebViews</title>
      <link>https://shadowfacts.net/2022/wkwebview-scroll-indicators/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2022/wkwebview-scroll-indicators/</guid>
      <pubDate>Fri, 14 Jan 2022 23:08:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><strong>Update: Since this post was published, the situation has changed and the workaround presented here is no longer valid. See the <a href="/2022/wkwebview-scroll-indicators-again/" data-link="/2022/wkwebview-scroll-indicators-again/">follow-up</a>.</strong></p>
<p>Here’s a stupid bug I ran into recently: if you’ve got a WKWebView in an iOS app and it shows something that’s not a normal webpage<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, the scroll indicator appearance switching doesn’t work. What I mean by that is, while the indicators appear correctly (with a dark color) when the system is in light mode, they do not take on a light color when dark mode is enabled. This renders the scroll indicators invisible against dark backgrounds, which can be annoying if you’re using the web view to display potentially lengthy content.</p>
<!-- excerpt-end -->
<p>Let’s say you’ve got a web view with some content that you want to match the system color scheme. The simplest way to do that is to set the web view’s background color to <code>.systemBackground</code> and add the following to the page’s stylesheet (to make the default text color change with the theme):</p>
<pre class="highlight" data-lang="css"><code>:<span class="hl-attr">root</span> {
    <span class="hl-prop">color-scheme</span>: light dark;
}
</code></pre>
<p>If you haven’t changed the page’s <code>background-color</code> in CSS, this will correctly make both the background and text colors follow the system theme. Unfortunately, it has no effect on the scroll indicator. Fixing that, it turns out, is rather involved.</p>
<p>The obvious thing to do would be to just set the <code>indicatorStyle</code> to <code>.default</code> on the web view’s internal scroll view, which should theoretically make the indicators automatically adjust to the color of the content inside the scroll view. Try this, however, and you’ll find that it does not work.</p>
<p>Regardless of where you set the property (in <code>viewDidLoad</code>, <code>viewWillAppear</code>, etc.) it appears to never be respected. Launching the app and checking <code>webView.scrollView.indicatorStyle</code> when paused in the view debugger reveals why: it’s been changed to <code>.black</code>:</p>
<pre class="highlight" data-lang="txt"><code>(lldb) po [(UIScrollView*)0x7fcc9e817000 indicatorStyle]
UIScrollViewIndicatorStyleBlack
</code></pre>
<p>Because WebKit—including, surprisingly, some the iOS platform-specific parts of it—is open source, we can go poking through it and try to figure out why this is happening. Looking for files with names like <code>WKWebView</code> in the WebKit source code reveals the promising-sounding <a href="https://github.com/WebKit/WebKit/blob/c2eb26fec22295b1bbd1d790f8492072b2c05447/Source/WebKit/UIProcess/API/ios/WKWebViewIOS.mm#L540-L556" data-link="github.com/WebKit/WebKit/blob/c2eb26fec2…"><code>WKWebViewIOS.mm</code></a>. Searching in that file for <code>indicatorStyle</code> reveals this lovely method:</p>
<pre class="highlight" data-lang="objective-c++"><code><span class="hl-op">-</span> (<span class="hl-type"><span class="hl-type">void</span></span>)_updateScrollViewBackground
{
    <span class="hl-kw">auto</span> <span class="hl-type">newScrollViewBackgroundColor</span> <span class="hl-op">=</span> <span class="hl-fn">scrollViewBackgroundColor</span>(<span class="hl-type">self</span>, <span class="hl-type">AllowPageBackgroundColorOverride</span>::<span class="hl-var">Yes</span>);
    <span class="hl-kw">if</span> (_scrollViewBackgroundColor <span class="hl-op">!=</span> newScrollViewBackgroundColor) {
        _scrollViewBackgroundColor <span class="hl-op">=</span> newScrollViewBackgroundColor;

        <span class="hl-kw">auto</span> <span class="hl-type">uiBackgroundColor</span> <span class="hl-op">=</span> <span class="hl-fn">adoptNS</span>([[<span class="hl-type">UIColor</span> <span class="hl-fn">alloc</span>] <span class="hl-fn">initWithCGColor</span>:<span class="hl-fn">cachedCGColor</span>(newScrollViewBackgroundColor)]);
        [<span class="hl-type">_scrollView</span> <span class="hl-fn">setBackgroundColor</span>:uiBackgroundColor.<span class="hl-fn">get</span>()];
    }

    <span class="hl-cmt">// Update the indicator style based on the lightness/darkness of the background color.</span>
    <span class="hl-kw">auto</span> <span class="hl-type">newPageBackgroundColor</span> <span class="hl-op">=</span> <span class="hl-fn">scrollViewBackgroundColor</span>(<span class="hl-type">self</span>, <span class="hl-type">AllowPageBackgroundColorOverride</span>::<span class="hl-var">No</span>);
    <span class="hl-kw">if</span> (newPageBackgroundColor.<span class="hl-fn">lightness</span>() <span class="hl-op">&lt;=</span> <span class="hl-num">.5f</span> <span class="hl-op">&amp;&amp;</span> newPageBackgroundColor.<span class="hl-fn">isVisible</span>())
        [<span class="hl-type">_scrollView</span> <span class="hl-fn">setIndicatorStyle</span>:UIScrollViewIndicatorStyleWhite];
    <span class="hl-kw">else</span>
        [<span class="hl-type">_scrollView</span> <span class="hl-fn">setIndicatorStyle</span>:UIScrollViewIndicatorStyleBlack];
}
</code></pre>
<p>“Update the indicator style based on the lightness/darkness of the background color.” Ah. That explains it. It’s examining the background color and using its lightness to explicitly set the scroll view’s indicator style to either white or black.</p>
<p>And why’s it overriding the <code>.default</code> that we set? Well, that method is called from (among other places) <code>_didCommitLayerTree</code>, which, as I understand it, is called whenever the remote WebKit process sends the in-process web view a new layer tree to display—so basically continuously.</p>
<p>Knowing that WebKit is using the page’s background color to set the indicator style, the answer seems simple, right? Just set the page’s background color in CSS to match the current color scheme. Wrong: setting <code>background-color: black;</code> on the <code>body</code> does not alter the scroll indicator color.</p>
<p>And why is that? It’s because <code>scrollViewBackgroundColor</code> ignores all other factors and returns a transparent black color if the web view is non-opaque. And since the <code>isVisible</code> method returns false for fully transparent colors, the scroll indicator style is being set to black.</p>
<p>Now, this is where the <a href="https://social.shadowfacts.net/notice/ABya4BV7zlJZrNKMkK" data-link="social.shadowfacts.net/notice/ABya4BV7zl…">minor iOS crimes</a> come in. If you can’t make the framework do what you want, change (read: swizzle) the framework.</p>
<p>So, in the <code>didFinishLaunching</code> app delegate callback, I swizzle the <code>_updateScrollViewBackground</code> method of <code>WKWebView</code>. My swizzled version calls the original implementation and then sets the scroll indicator mode back to <code>.default</code>, superseding whatever WebKit changed it to. And this finally makes the scroll indicator visible in dark mode.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private func</span> swizzleWKWebView() {
	<span class="hl-kw">let</span> selector = <span class="hl-type">Selector</span>((<span class="hl-str">"_updateScrollViewBackground"</span>))
	<span class="hl-kw">var</span> originalIMP: <span class="hl-type">IMP</span>?
	<span class="hl-kw">let</span> imp = <span class="hl-fn">imp_implementationWithBlock</span>({ (self: <span class="hl-type">WKWebView</span>) <span class="hl-kw">in
		if let</span> originalIMP = originalIMP {
			<span class="hl-kw">let</span> original = <span class="hl-fn">unsafeBitCast</span>(originalIMP, to: (<span class="hl-kw">@convention</span>(c) (<span class="hl-type">WKWebView</span>, <span class="hl-type">Selector</span>) -&gt; <span class="hl-type">Void</span>).<span class="hl-kw">self</span>)
			<span class="hl-fn">original</span>(<span class="hl-kw">self</span>, selector)
		}
		
		<span class="hl-kw">self</span>.<span class="hl-prop">scrollView</span>.<span class="hl-prop">indicatorStyle</span> = .<span class="hl-prop">default</span>
		
	} <span class="hl-kw">as</span> (<span class="hl-kw">@convention</span>(block) (<span class="hl-type">WKWebView</span>) -&gt; <span class="hl-type">Void</span>))
	originalIMP = <span class="hl-fn">class_replaceMethod</span>(<span class="hl-type">WKWebView</span>.<span class="hl-kw">self</span>, selector, imp, <span class="hl-str">"v@:"</span>)
	<span class="hl-kw">if</span> originalIMP == <span class="hl-kw">nil</span> {
		<span class="hl-fn">os_log</span>(.<span class="hl-prop">error</span>, <span class="hl-str">"Missing originalIMP for -[WKWebView _updateScrollViewBackground], did WebKit change?"</span>)
	}
}
</code></pre>
<p>A couple things to note about this code:</p>
<p><code>originalIMP</code>, is optional because <code>class_replaceMethod</code> returns nil if the method does not already exist, though it still adds our new implementation. If this happens, I log a message because it probably means that WebKit has changed and hopefully this hack is no longer necessary (or that it may need updating).</p>
<p>The <code>unsafeBitCast</code> is necessary because an <a href="https://developer.apple.com/documentation/objectivec/objective-c_runtime/imp" data-link="developer.apple.com/documentation/object…"><code>IMP</code></a> is in fact a C function pointer but it’s imported into Swift as an OpaquePointer.</p>
<p>The Swift closure is cast to an Objective-C block because, although <code>imp_implementationWithBlock</code> is imported into Swift as taking a parameter of type <code>Any</code>, what it really needs, as the name implies, is a block.</p>
<p>The “v@:” string is the Objective-C <a href="https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100" data-link="developer.apple.com/library/archive/docu…">type encoding</a> of the method’s signature (it returns void, takes the self instance, and the selector for the method being called).</p>
<p>And with that annoying workaround, the scroll indicator is finally visible in dark mode (and updates correctly when switching color schemes). Given that <code>WKWebView</code> gives some consideration to the system color scheme, I’m inclined to think this is a bug, so hopefully it’ll be fixed eventually. Alternatively, as I noted, that part of WebKit is in fact open source, so an intrepid developer could fix it themself…</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I say this because the only way I’ve tested it is by generating some HTML and giving it to <code>loadHTMLString(_:baseURL:)</code>. It’s entirely possible this is not a factor. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>M1 Max MacBook Pro Review</title>
      <link>https://shadowfacts.net/2022/m1-max/</link>
      <category>computers</category>
      <guid>https://shadowfacts.net/2022/m1-max/</guid>
      <pubDate>Thu, 06 Jan 2022 19:37:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Here’s the review, if you’re not going to read any farther than the first sentence: this is a damn good computer. I’ve had my M1 Max MBP (32 GPU cores, 64 GB RAM) for two months now and, aside from the time spent migrating things off my previous computer, it’s been the only “real” computer I’ve used in that time. </p>
<!-- excerpt-end -->
<p>Before I get into the details, some context: My previous primary computer was a 16” Intel MacBook Pro (8 cores, 32 GB of memory, and a Radeon 5500M). I got it almost immediately when it came out in the fall of 2019, coming from the original 2012 15” Retina MacBook Pro. When I first got it, it was the best (for definitions of best relating to the specifics I care about, primarily CPU performance) laptop Apple had made. What’s more it was the first laptop to get rid of the accursed butterfly keyboard. I had zero complaints about it when I first got it, and for a good while afterwards.</p>
<p>But then, last winter, shortly after they came out, I <a href="/2021/m1/" data-link="/2021/m1/">used</a> an M1 Mac mini (8-core, 16 GB RAM) for a couple months. For all I’d fawned over the 2019 MBP, the M1 Mac mini (as well as other people’s reviews of the corresponding laptops) gave me a newfound disappointment of all of my laptop’s shortcomings. Well, most of the issues I had actually stem from one particular shortcoming: the Intel processor. It was by no means a slouch, it was just pitifully constrained by the thermal design of the laptop. Any sustained workload would cause it to drop down to the base clock—though not throttle below, an improvement over previous generations—and leave a good deal of performance on the table (particularly when playing games, with the GPU also dumping heat into the shared cooler). The abysmal battery life and incredible noise and heat that went along with it didn’t do anything to help (a laptop whose processor routinely runs at 100°C cannot rightfully be called a <em>lap</em>top). The M1, by contrast, had none of those issues. In addition to having better single-core performance, it was almost comically more power-efficient. At the end of my M1 review, I said that I was incredibly excited to see what the future of the new Mac architecture held, and I was not disappointed.</p>
<h2 id="hardware"><a href="#hardware" class="header-anchor" aria-hidden="true" role="presentation">##</a> Hardware</h2>
<p>First off, the most important part: SoC performance. This machine handily beats my old laptop in literally everything I do.</p>
<p>I could list a bunch of artificial benchmarks for you <em>oooh</em> and <em>ahhh</em> at, but that’s not generally representative of what I actually use it for.</p>
<p>A full release build of Tusker, my iOS app for Mastodon, takes about 94 seconds on my Intel laptop. It’s 44% faster on the M1 Max, taking 53 seconds. Debug builds with incremental compilation see a similar improvement. And, as was the case with the M1, where the single-core performance really shines is in reducing the feedback loop between making a code change and being able to see that reflected in the running app. </p>
<p>As with the Mac mini, everything just <em>feels</em> faster. No doubt some of that is a placebo, but not entirely. I can put my old and new laptops side by side and launch the same app, and the Apple Silicon one will appear on screen several seconds sooner. This snappiness extends to within apps too, especially ones using Catalyst which always felt a little bit unresponsive before.</p>
<p>The other incredible improvement Apple Silicon brings is the power and thermal efficiency. It’s not quite as miraculous as the M1 Mini, whose fans I could never get to spin up, but it’s still a vast improvement. I have to be pushing both the CPU and the GPU fairly hard (e.g., by playing a graphically intensive game) in order to get the fans to be audible. Ordinary, CPU-focused workloads don’t cause the fans to ramp up and barely make the laptop feel warm. I once accidentally left a script running that was consuming 100% CPU all day because the fans weren’t there to signal that something was amiss.</p>
<p>Compare that to the Intel MBP whose fans sound like they’re about to take flight if you so much as look at the machine wrong (seriously, the fans spun up to audible levels and the chassis felt burning hot while it was just in target disk mode).</p>
<p>This has big ramifications on the battery front. When I’d be using my Intel laptop on the go and not be expecting to charge for a while, I’d use <a href="http://tbswitcher.rugarciap.com/" data-link="tbswitcher.rugarciap.com">Turbo Boost Switcher</a> to disable Intel Turbo Boost and limit the CPU clock speed to the base level. This sligtly degrades performance, but substantially improves battery life. The Apple Silicon laptop, by contrast, achieves in normal mode the battery life that the Intel machine did with turbo disabled. If I were to put this into Low Power Mode (which, as I understand it, partly works by limiting processor speed), I don’t even know how long it would last.</p>
<h3 id="display"><a href="#display" class="header-anchor" aria-hidden="true" role="presentation">###</a> Display</h3>
<p>The display on this computer is great. Having had a high-refresh rate external monitor for several years, my expectation was that the 120Hz support would be the thing I’d enjoy the most. But, that hasn’t actually been the case. Sure, 120Hz is great, but over the past couple months, I’ve been using my laptop undocked more frequently, and what I’ve come to really appreciate is the true retina pixel density.</p>
<p>If you don’t know, Apple laptops starting with the 2016 MacBook Pro have used non-integer scaling factors. That is, by default they ran at point resolutions which were more than half of the pixel resolution in each dimension. So, a software pixel mapped to some fraction of a hardware pixel, meaning everything had to be imprecisely scaled before actually going to the panel. People have been complaining about this for years, and I’d always dismissed it because I never observed the issue. But, in hindsight, that’s because the vast majority of my laptop usage was with it docked to an external monitor and peripherals. In that scenario, the laptop’s builtin display ends up physically far enough away from my eyes that I don’t perceive any blurriness. But, since I’ve been using this laptop more as an actual laptop—bringing the screen a good foot or two closer to my eyes—I’ve noticed that text is undeniably crisper.</p>
<aside>
<p>Using the screen on this laptop, particularly when using it undocked and independent of an external monitor has firmly convinced me of something I previously believed: the ideal monitor would be 5k (i.e., 2560x1440 at the Retina 2x pixel density), 27“ diagonally, and 120Hz. My current external monitor is 1440p, 27“, and 144Hz and having used a monitors of that size for years and years, I think it’s the best combination of screen real-estate and physical size of UI elements. Using a 5k iMac screen in the office<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> convinced me that high-DPI is very nice, even if you’re just looking at text all day. And finally seeing a screen that is both high DPI and high refresh rate has validated that belief. I really hope that someone makes a monitor that manages to include both.</p>
</aside>
<p>All that said, 120Hz is of course also great. Scrolling and mousing around and animations all feel smoother, because they are. In particular the trackpad feels even more responsive and natural. And the mini-LED backlight makes HDR content look <em>great</em>. Seriously, if I were watching a movie or something on my computer, I’d prefer to watch it on the smaller builtin display than my big external monitor just for the vastly better contrast and black levels. Some people have complained about haloing (when you can see the backlight zone illuminated because there’s a small, bright object against a dark background), but I’ve never noticed except when deliberately looking for it by moving the cursor around on a black screen.</p>
<p>The one very minor complaint I do have is that ghosting on the display seems noticeably worse than my external monitor. I don’t have any way of objectively testing this, but moving the mouse cursor around seems to leave a longer trail. But again, I only actually notice that when I’m looking for it. In normal usage, and even in playing games, it isn’t apparent.</p>
<p>And, lastly about the display, let’s talk about the notch, since everyone needs to have an opinion on it. The short version is I don’t give a crap about it. The longer version is I really do not give a crap about it. Since the first week I had the computer, this is the only time I’ve given it any thought. It sits in the middle of the menu bar where nothing’s visible anyway (helped by having the 16” rather than the 14”, where big menus or many menubar items are more likely to overflow their respective halves), so it’s never once caused a problem for me.</p>
<h3 id="hardware-miscellany"><a href="#hardware-miscellany" class="header-anchor" aria-hidden="true" role="presentation">###</a> Hardware Miscellany</h3>
<p>MagSafe is wonderful, I’m very happy it’s back. I get a little spark of joy when I walk up to my laptop and I see the little green dot on the connector. I pulled out my old laptop the other day to begin the process of erasing it before it can be sold, and because it had been sitting for so long, the battery was completely dead. I plugged in a USB-C charger and experienced a mild flash of annoyance that I had know way of knowing whether it was delivering power, other than to wait several minutes for the machine to boot up.</p>
<p>While I didn’t hate the Touch Bar as much as some people, I never found it to be better than plain old function keys. Nonetheless, I’m perfectly happy that it’s gone. My stupid minor gripe about the Touch Bar was that, when I’m using my computer docked with an external monitor and keyboard, the Touch Bar would remain on and active. That doesn’t sound so bad, but it becomes an annoyance as I interact with apps and see the software buttons on the Touch Bar changing and flashing in the corner of my eye. The removal of the Touch Bar has dealt with that annoyance and has made absolutely no difference to my productivity when using the laptop on its own, so I’m happy.</p>
<p>The hardware changes with this machine can be divided into two categories: Apple Silicon-related and not. The non-Apple Silicon changes by themselves are fairly small, but they represent a marked quality-of-life improvement when just using the computer. </p>
<h2 id="software"><a href="#software" class="header-anchor" aria-hidden="true" role="presentation">##</a> Software</h2>
<p>With the M1 Mac mini, I had both ARM-native Homebrew and Intel-under-Rosetta Homebrew installed, in case I needed to install tools from brew that only ran under Rosetta. This time, that’s been entirely unnecessary<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>. The one Rosetta package I installed last time, iPerf, now runs natively. In general, I’ve had to use Rosetta for far fewer things than I expected (with the notable exception of games), and even when I have, it’s been impressively stable and performant.</p>
<h3 id="games"><a href="#games" class="header-anchor" aria-hidden="true" role="presentation">###</a> Games</h3>
<p>Video games are where the Apple Silicon software story gets complicated. Gaming on the Mac has always been a tenuous proposition and ARM has added a whole set of fun, new complications.</p>
<p>Here’s my rule of thumb for guessing how well a game will run on macOS: If it’s compiled for ARM or it uses Metal directly (not through a translation layer like OpenGL<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup> or MoltenVK), it’ll probably run great. If not, all bets are off. If a game’s either built for ARM or uses Metal, chances are someone has put at least some effort into getting it to work on Macs, so letting it have the most powerful CPU and GPU that’s ever been in a Mac laptop (which come close to being the most powerful in a Mac, period) gives it a really good chance of running well.</p>
<p>Rise of the Tomb Raider and Shadow of the Tomb Raider both run under Rosetta and use Metal and they run shockingly well. Each can manage a fairly consistent 60 FPS at 1440p on high graphical settings. The HDR support in Shadow of the Tomb Raider even worked with the built-in display. It wasn’t quite the difference in visual fidelity that I expected, but I was pleasantly surprised it worked at all.</p>
<p>Minecraft runs well, once you’ve got a JVM installed that’s built for ARM and the LWJGL natives swapped out with ones compiled for ARM, since the set Minecraft ships isn’t. (<a href="https://gist.github.com/nikhiljha/7313ac5553aafb1c8596b1fca0f4cdff" data-link="gist.github.com/nikhiljha/7313ac5553aafb…">These</a> instructions explain how to replace the native libs when using MultiMC<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>. Though I use the Temurin JDK, not whatever’s in Homebrew.)</p>
<p>Cities: Skylines doesn’t see much of a graphical difference, but does benefit from the faster CPU. My old laptop can’t simulate the most recent city I built at 3x speed without things like vehicle movement appearing noticeably jerky whereas the Apple Silicon one can handle it. That said, I haven’t spent enough time playing C:S on it to know if the ceiling (that is to say, how much farther I could grow my city before I encountered performance issues) is substantially higher.</p>
<p>I haven’t tried many others, but I’m fairly confident that anything that previously ran on macOS and isn’t too graphically demanding will run well. That includes games like Hades and Into the Breach.</p>
<h3 id="software-miscellany"><a href="#software-miscellany" class="header-anchor" aria-hidden="true" role="presentation">###</a> Software Miscellany</h3>
<p>Beyond games, 1Password 6 remains the only app I regularly use that needs Rosetta and it continues to work flawlessly.</p>
<p>Being able to run iOS apps natively is awesome, even if I don’t use it terribly often. The iOS app I use most on the Mac is Overcast, my preferred podcast player. It’s quite nice to be able to get notifications when new episodes are available right on my computer, rather than having to check my phone, as well as being able to listen without using the web interface.</p>
<p>Electron apps that are compiled for ARM, such as Spotify, are more responsive too. But it’s mostly a reflection of the performance/efficiency of Apple Silicon that it’s able to compensate for the bloat that is Electron. However, when it comes to Electron/browser-based apps that aren’t compiled for ARM (<em>cough</em> Steam <em>cough</em>), things aren’t as good. Steam does run and functions properly, but interacting with anything it uses embedded Chromium for (most of the application) is painfully slow and unresponsive.</p>
<p>One of my few complaints about the M1 Mac mini was resolved with the release of macOS Monterey: Apple Silicon Macs can now use DDC commands to control external displays. Not being able to control the of my external monitors was never a huge issue, but it was a persistent inconvenience that I’m glad has been resolved.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>Overall, this is a fantastic computer. Apple Silicon means it’s vastly faster and more efficient than any previous Mac laptop. As with last year, I’m impresed how much software is already native—just a year and a half into the Mac’s ARM transition—and how well Rosetta 2 works for software that isn’t. Beyond Apple Silicon, this laptop is an upgrade in every single way over the few preceding generations which felt like a big regression. Two laptops ago, I was using the 7.5 year old 2012 Retina MacBook Pro: the first laptop of a new generation of MacBooks. I’m hopeful that with all these long-standing issues resolved, this machine will last a similarly long time.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>haha, remember those <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>I’ve also largely switched from Homebrew to MacPorts, but that’s a blog post for another time. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>No, OpenGL itself is not a translation layer. But OpenGL on Apple Silicon works by translating everything to Metal. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>Those only apply for Minecraft versions recent enough to use LWJGL 3. Getting earlier versions running natively is possible, but a fair bit more involved. Perhaps a subject for <a href="/2022/lwjgl-arm64/" data-link="/2022/lwjgl-arm64/">another time</a>. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Automatically Changing Scroll Direction Based on USB Devices</title>
      <link>https://shadowfacts.net/2021/auto-switch-scroll-direction/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2021/auto-switch-scroll-direction/</guid>
      <pubDate>Sun, 19 Sep 2021 19:17:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><a href="/2021/scrollswitcher/" data-link="/2021/scrollswitcher/">Last time</a> I wrote about programmatically toggling natural scrolling on macOS and building a menu bar app to let me do that quickly. It works very well, but there’s still a little bit of friction with having to click the menu bar icon—or, more accurately, forgetting to click it and then scrolling backwards and realizing you forgot. As I mentioned at the end of my previous post, one obvious way to extend it, now that the ability to programmatically set direction is in place, would be toggling it automatically based on what’s currently connected. This turned out to not be terribly complicated, but dealing with IOKit was somewhat annoying, so I thought I’d write it up.</p>
<!-- excerpt-end -->
<h2 id="watching-for-usb-device-changes"><a href="#watching-for-usb-device-changes" class="header-anchor" aria-hidden="true" role="presentation">##</a> Watching for USB Device Changes</h2>
<p>Some cursory googling quickly led me to the IOKit documentation, which describes an <code>IODeviceManager</code> that sounds exactly like what I wanted. Unfortunately, the docs are rather lacking—there’s not a single example of how to actually set it up (and it’s not helped by the fact that it’s all old CoreFoundation style, nor that the Swift overlay is very poor).</p>
<p>But, with a combination of the docs and the open source <a href="https://github.com/icculus/manymouse/blob/main/macosx_hidmanager.c" data-link="github.com/icculus/manymouse/blob/main/m…">ManyMouse project</a><sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, I managed to get it working.</p>
<pre class="highlight" data-lang="swift"><code>manager = <span class="hl-type">IOHIDManagerCreate</span>(kCFAllocatorDefault, <span class="hl-num">0</span>, <span class="hl-cmt">/* kIOHIDManagerOptionNone */</span>)
</code></pre>
<p>First, you need to create a manager using <code>IOHIDManagerCreate</code>. It takes an allocator and the options to use. I’m using the literal 0 here because the constants for the options are not imported into Swift. You also need to retain the manager for as long as you want to observe changes, so I’m storing it here in an instance var on my app delegate.</p>
<p>After that, you tell the manager what sorts of devices you care about. You do this by creating a dictionary that matches against the properties of a device.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> dict = <span class="hl-type">IOServiceMatching</span>(kIOHIDDeviceKey)! <span class="hl-kw">as</span>! [<span class="hl-type">String</span>: <span class="hl-type">Any</span>]
dict[kIOHIDDeviceUsagePageKey] = kHIDPage_GenericDesktop
dict[kIOHIDDeviceUsageKey] = kHIDUsage_GD_Mouse
<span class="hl-type">IOHIDManagerSetDeviceMatching</span>(manager, dict <span class="hl-kw">as</span> <span class="hl-type">CFDictionary</span>)
</code></pre>
<p>The <code>IOServiceMatching</code> function takes the name of a service type, for which we pass the redundantly named HID device key. It returns a <code>CFDictionary</code>, which I convert into a Swift dictionary, so that I can set properties on it more easily than dealing with <code>CFDictionarySetValue</code> from Swift. The filter is further refined by limiting it to the USB Generic Desktop usage page<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>, and specifically the mouse usage (the USB HID spec makes no differentiation between kinds of pointing devices, they’re all just mice), before setting the matching dictionary on the manager.</p>
<p>After that, we register callbacks for device addition and removal and add the manager to the run loop.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-type">IOHIDManagerRegisterDeviceMatchingCallback</span>(manager, <span class="hl-fn">hidDeviceAdded</span>(context:result:sender:device:), <span class="hl-kw">nil</span>)
<span class="hl-type">IOHIDManagerRegisterDeviceRemovalCallback</span>(manager, <span class="hl-fn">hidDeviceRemoved</span>(context:result:sender:device:), <span class="hl-kw">nil</span>)

<span class="hl-type">IOHIDManagerScheduleWithRunLoop</span>(manager, <span class="hl-type">CFRunLoopGetCurrent</span>(), <span class="hl-type">CFRunLoopMode</span>.<span class="hl-prop">commonModes</span>.<span class="hl-prop">rawValue</span>)
</code></pre>
<p>One important thing to note about the callbacks is that, since this is all old CoreFoundation-style code and not Objective-C, they’re not blocks, they’re C function pointers. Specifically, <a href="https://developer.apple.com/documentation/iokit/iohiddevicecallback" data-link="developer.apple.com/documentation/iokit/…"><code>IOHIDDeviceCallback</code></a>s. This means that, when providing one from Swift, the value can either be a global function or a closure that does not capture anything. In either case, this requirement is because a C function pointer needs to point to a specific point in our binary, which can’t encode additional information like captures or the method receiver. To help alleviate this, the callback registration functions take a third parameter, a void pointer to some context that will be provided to the function when it’s called. All of the contextual information I need in the callbacks can be stored on the app delegate, though, so I don’t bother with trying to make the <code>void *context</code> shenanigans play nicely with Swift.</p>
<p>The callback functions receive an <code>IOHIDDevice</code>, the functions for which appear to only be documented in the headers. The first thing I do in the callbacks is get the name and usage for the device:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> hidDeviceAdded(context: <span class="hl-type">UnsafeMutableRawPointer</span>?, result: <span class="hl-type">IOReturn</span>, sender: <span class="hl-type">UnsafeMutableRawPointer</span>?, device: <span class="hl-type">IOHIDDevice</span>) {
    <span class="hl-kw">guard let</span> name = <span class="hl-type">IOHIDDeviceGetProperty</span>(device, kIOHIDProductKey <span class="hl-kw">as</span> <span class="hl-type">CFString</span>) <span class="hl-kw">as</span>? <span class="hl-type">String</span>,
          <span class="hl-kw">let</span> usage = <span class="hl-type">IOHIDDeviceGetProperty</span>(device, kIOHIDPrimaryUsageKey <span class="hl-kw">as</span> <span class="hl-type">CFString</span>) <span class="hl-kw">as</span>? <span class="hl-type">UInt32</span> <span class="hl-kw">else</span> {
        <span class="hl-fn">fatalError</span>()
    }
}
</code></pre>
<p>The get property function returns an optional <code>CFTypeRef</code> so we have to try and cast it to the appropriate type.</p>
<p>The usage we need because, even though the manager’s filter is set to only allow Mouse devices through, the callback is still sometimes invoked with a device of type <code>kHIDUsage_GD_SystemControl</code> for reasons that I can’t discern. So, as a precaution, I silently ignore devices which don’t have the right usage:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> hidDeviceAdded(context: <span class="hl-type">UnsafeMutableRawPointer</span>?, result: <span class="hl-type">IOReturn</span>, sender: <span class="hl-type">UnsafeMutableRawPointer</span>?, device: <span class="hl-type">IOHIDDevice</span>) {
	<span class="hl-cmt">// ...</span>
    <span class="hl-kw">guard</span> usage == kHIDUsage_GD_Mouse <span class="hl-kw">else</span> { <span class="hl-kw">return</span> }
}
</code></pre><h2 id="determining-device-types"><a href="#determining-device-types" class="header-anchor" aria-hidden="true" role="presentation">##</a> Determining Device Types</h2>
<p>The next task is determining whether the given device is a mouse or trackpad. Unfortunately, as I mentioned, the USB HID spec doesn’t differentiate between mice and trackpads, and I couldn’t find any <code>IOHIDDevice</code> properties that did either. So, we have to come up with our own heuristics based on the information we do have access. Here’s where it gets really dumb:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> deviceNameIsProbablyTrackpad(<span class="hl-kw">_</span> name: <span class="hl-type">String</span>) -&gt; <span class="hl-type">Bool</span> {
    <span class="hl-kw">return</span> name.<span class="hl-fn">lowercased</span>().<span class="hl-fn">contains</span>(<span class="hl-str">"trackpad"</span>)
}
</code></pre>
<p>Yep. I figure this should cover most (at least Apple ones, which do call themselves “trackpad“s). And what makes something a mouse? Well, if it’s not a trackpad. Yes, it’s a bit ridiculous, but it works well enough.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> hidDeviceAdded(context: <span class="hl-type">UnsafeMutableRawPointer</span>?, result: <span class="hl-type">IOReturn</span>, sender: <span class="hl-type">UnsafeMutableRawPointer</span>?, device: <span class="hl-type">IOHIDDevice</span>) {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">let</span> delegate = <span class="hl-type">NSApp</span>.<span class="hl-prop">delegate</span> <span class="hl-kw">as</span>! <span class="hl-type">AppDelegate</span>
    <span class="hl-kw">if</span> <span class="hl-fn">deviceNameIsProbablyTrackpad</span>(name) {
        delegate.<span class="hl-prop">trackpadCount</span> += <span class="hl-num">1</span>
    } <span class="hl-kw">else</span> {
        delegate.<span class="hl-prop">mouseCount</span> += <span class="hl-num">1</span>
    }
}
</code></pre>
<p>We track the actual counts of trackpads and mice rather than just whether one is connected or not, because in the device removed callback, it saves have to re-enumerate all connected devices in case there were multiple of one type connected and only one was removed.</p>
<p>The device removed callback does pretty much the same thing, just subtracting instead of adding 1 to the respective counts.</p>
<h2 id="automatically-switching-scroll-direction"><a href="#automatically-switching-scroll-direction" class="header-anchor" aria-hidden="true" role="presentation">##</a> Automatically Switching Scroll Direction</h2>
<p>After the counts are updated, the delegate is notified that devices have changed and told to update the scroll direction. This is done by sending void to a <code>PasstroughSubject</code> on the app delegate. I use Combine rather than just calling a method on the app delegate because, when the <code>IOHIDManager</code> is initially added, it fires the added callback a whole bunch of times for every device that’s already connected. Additionally, when a USB hub is added/removed we get callbacks for all of the individual devices and I don’t want to flip the scroll direction a bunch of times. So, when the app delegate listens to the subject, it uses Combine’s debouncing to only fire every 0.1 seconds at most.</p>
<p>Actually changing the scroll direction is next, which requires figuring out what direction to change it to, based on the current device counts. This has a slight complication: laptops. </p>
<p>On a laptop, the trackpad is always connected, so if we were to switch to natural scrolling whenever a trackpad was connected, that would be useless. Similarly, for desktops, you can imagine a case where a mouse is always connected, so just switching to normal when a mouse is present would be similarly ineffectual in some cases. The solution? Doing both, and having a preference to let the user choose.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private func</span> updateDirectionForAutoMode() {
    <span class="hl-kw">switch</span> <span class="hl-type">Preferences</span>.<span class="hl-prop">autoMode</span> {
	<span class="hl-kw">case</span> .<span class="hl-prop">disabled</span>:
        <span class="hl-kw">return

	case</span> .<span class="hl-prop">normalWhenMousePresent</span>:
        <span class="hl-fn">setDirection</span>(mouseCount &gt; <span class="hl-num">0</span> ? .<span class="hl-prop">normal</span> : .<span class="hl-prop">natural</span>)

	<span class="hl-kw">case</span> .<span class="hl-prop">naturalWhenTrackpadPresent</span>:
        <span class="hl-fn">setDirection</span>(trackpadCount &gt; <span class="hl-num">0</span> ? .<span class="hl-prop">natural</span> : .<span class="hl-prop">normal</span>)
    }
}

</code></pre>
<p>This way, on a laptop you can set it to “normal when mouse present” and on a desktop with an always-connected mouse you can set it to “natural when trackpad present”.</p>
<p>I’ve been using this updated version of ScrollSwitcher for about a week now, and it’s worked flawlessly. I haven’t had to open System Preferences or even click the menu bar icon. If you’re interested, the full source code for the app can be found here: <a href="https://git.shadowfacts.net/shadowfacts/ScrollSwitcher" data-link="git.shadowfacts.net/shadowfacts/ScrollSw…">https://git.shadowfacts.net/shadowfacts/ScrollSwitcher</a>.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Archived: <a href="https://archive.is/NvmMN" data-link="archive.is/NvmMN">https://archive.is/NvmMN</a> <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>USB HID usage page and usage tables can be found here: <a href="https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf" data-link="usb.org/sites/default/files/documents/hu…">https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf</a>. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>A Mac Menu Bar App to Toggle Natural Scrolling</title>
      <link>https://shadowfacts.net/2021/scrollswitcher/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2021/scrollswitcher/</guid>
      <pubDate>Fri, 03 Sep 2021 02:31:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>There are two ways you can configure the scroll direction on a computer: normal or inverted (what Apple calls “natural”). If you, like most, use a mouse with a wheel, the normal scrolling scheme is probably what you use. Under it, moving the mouse wheel down (i.e., so that a point on the top of the wheel moves down/closer to you) causes the content to move <em>up</em> and the viewport (the window into the content) to move down. Similarly on a multitouch trackpad, under normal scrolling, as your two fingers move down, the content moves up and the viewport down.</p>
<p>The other option—natural scrolling—flips this, so as your fingers move down on the trackpad, <em>the content moves down</em> and viewport moves up, and similarly for rotating the mouse wheel. When using a mouse, this feels to most people obviously backwards. But this setting doesn’t exist without reason. It’s transposing the paradigm of touchscreens on to the trackpad, where your finger remains pinned to the point in the content where you first touched. You move the content directly, rather than moving the viewport.</p>
<p>This generally isn’t a big deal; most people just find the mode they prefer, change the setting, and then never touch it again. But what if you prefer both?</p>
<!-- excerpt-end -->
<p>Why might you want both options for this preference? Well, I use my laptop both docked and undocked. When it’s at my desk and connected to a monitor, I also use a keyboard and mouse, for which I want normal scrolling. But when it’s undocked and I’m using the trackpad, I vastly prefer natural scrolling.</p>
<aside>
<p>Yes, I recognize that I’m probably in the tiny minority of people who prefer this. Could I try myself to accept the wrong way of scrolling on either the trackpad or mouse and then just leave the setting alone? <a href="https://www.youtube.com/watch?v=MFzDaBzBlL0" data-link="youtube.com/watch?v=MFzDaBzBlL0">Probably</a>. But I don’t really want to.</p>
</aside>
<p>Unfortunately, macOS only has one setting shared between both mice and trackpads. Yes, despite appearing in two different places in System Preferences (under both Trackpad and Mouse), with the implication that they’re two different settings, both checkboxes are backed by the same underlying value. So, we need some workaround.</p>
<p>Since the preference can’t be different for mouse and trackpad, I at least want some way of toggling it quickly, so that when I dock/undock my laptop, I can correct it quickly. A menu bar app seemed like the perfect fit, as I don’t want something cluttering up my dock and there’s almost no UI (but I do want more than just a global keyboard shortcut).</p>
<p>A bit of cursory googling reveals that the actual preference is stored in a global user default, <code>com.apple.swipescrolldirection</code>. It’s a boolean value, and true means natural scrolling is enabled. Reading it is easy, but unfortunately setting it is a bit more complicated. Just using <code>defaults write -g com.apple.swipescrolldirection -bool YES</code> on the command line does not change the actual value—although when you open the Mouse preferences pane<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, you can see the checkbox state has indeed opened. Places on the internet mention this and say you need to log out and back in to correct the input behavior. But, that isn’t necessary when you change in System Preferences, so clearly something more is going on.</p>
<p>To try and figure out what else System Preferences was doing, I launched Console.app and started streaming log messages. I toggled the scroll direction checkbox a bunch of times in both the Mouse and Trackpad pref panes before stopping log streaming. Searching the logs for “mouse” and “trackpad” revealed nothing useful, but searching for “scroll” turned up one beautiful nugget of information:</p>
<img src="/2021/scrollswitcher/console.png" alt="Console.app showing a message from disnoted reading register name: SwipeScrollDirectionDidChangeNotification">
<p><code>SwipeScrollDirectionDidChangeNotification</code>. Sure sounds like the name of a notification that would be fired to inform other parts of the OS about the change.</p>
<p>With at least an inkling of the direction I needed to go in, I started building the app with the initial goal of displaying the current scrolling mode. I created a new Mac app in Xcode and set the <code>LSUIElement</code> key in the <code>Info.plist</code> to <code>YES</code>, hiding the app from the dock. I also removed the Xcode-created storyboard and then created a system status bar item with a dummy icon.</p>
<h2 id="fun-problem-#1-storyboards-or-the-lack-thereof"><a href="#fun-problem-#1-storyboards-or-the-lack-thereof" class="header-anchor" aria-hidden="true" role="presentation">##</a> Fun Problem #1: Storyboards, or the lack thereof</h2>
<p>Here is where I encountered the first (small) problem: the menu item never appeared. My code that created the menu item in <code>applicationDidFinishLaunching</code> was being hit, as evidenced by a breakpoint, and the menu item was being created, but it just didn’t show up. I recalled noticing that the docs of <code>NSStatusBar.statusItem(withLength:)</code> mentioned that the <code>NSStatusItem</code> had to be retained by the caller, otherwise it would be removed when deallocated. But that shouldn’t have been the problem, as I was storing it in a property on my app delegate. Unless…</p>
<p>It turns out using <code>@main</code> on your app delegate does not strongly retain it unless there’s a storyboard, which I had deleted. To fix it, I had to replace the <code>@main</code> with a custom <code>main.swift</code> which creates the <code>NSApplication</code> instance and strongly retains the delegate.</p>
<h2 id="reading-the-scroll-direction"><a href="#reading-the-scroll-direction" class="header-anchor" aria-hidden="true" role="presentation">##</a> Reading the Scroll Direction</h2>
<p>With the menu item now displaying, it was time to read the current scroll direction. The most direct mapping from what I did on the command line would be to use Foundation’s <code>Process</code> to actually run the <code>defaults</code> command and then examine the output. But, because the value we’re after is stored in the <a href="https://developer.apple.com/documentation/foundation/nsglobaldomain" data-link="developer.apple.com/documentation/founda…">global domain</a>, it should be—and indeed is—accessible to us directly through <code>UserDefaults</code>.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> defaultsKey = <span class="hl-str">"com.apple.swipescrolldirection"</span>

<span class="hl-kw">enum</span> Direction: <span class="hl-type">Int</span>, <span class="hl-type">Equatable</span> {
    <span class="hl-kw">case</span> normal, natural

    <span class="hl-kw">static var</span> current: <span class="hl-type">Direction</span> {
        <span class="hl-kw">let</span> naturalEnabled = <span class="hl-type">UserDefaults</span>.<span class="hl-prop">standard</span>.<span class="hl-fn">bool</span>(forKey: defaultssKey)
        <span class="hl-kw">return</span> naturalEnabled ? .<span class="hl-prop">natural</span> : .<span class="hl-prop">normal</span>
    }
}
</code></pre>
<p>Then, using the current direction, I could set the menu bar item’s icon. SF Symbols’ <code>scroll.filled</code> for natural scrolling and <code>scroll</code> for normal.</p>
<figure>
  <img src="/2021/scrollswitcher/scrolls.png" alt="filled and outlined scroll icons in the menubar">
  <figcaption>Scrolls, get it?</figcaption>
</figure>
<h2 id="setting-the-scroll-direction"><a href="#setting-the-scroll-direction" class="header-anchor" aria-hidden="true" role="presentation">##</a> Setting the Scroll Direction</h2><aside class="inline">
<p>I briefly toyed with using <code>UserDefaults</code> to also set the scroll direction directly, without having to shell out to the <code>defaults</code> command. The obvious thing to do would be to construct a <code>UserDefaults</code> for the global suite. But sadly, no luck:</p>
<pre class="highlight" data-lang="txt"><code>Using NSGlobalDomain as an NSUserDefaults suite name does not make sense and will not work.
</code></pre>
<p>My next idea was calling <code>setPersistentDomain(_:forName:)</code> on the standard <code>UserDefaults</code> with the a dictionary containing the new scroll direction for the global domain. This was, not to overstate things, a colossal failure. The docs for this method say:</p>
<blockquote>
<p>Calling this method is equivalent to initializing a user defaults object with <code>init(suiteName:)</code> passing <code>domainName</code>, and calling the <code>set(_:forKey:)</code> method for each key-value pair in domain.</p>
</blockquote>
<p>What actually happened was calling with <code>UserDefaults.globalDomain</code> reset a whole bunch of global defaults (though seemingly not all) across my entire computer. This included (but may not have been limited to): a number of General system prefs (including what first tipped me off that something went wrong, the accent color), and the Xcode 13 setting for showing file extensions.</p>
<p>Pro tip: do not call <code>setPersistentDomain</code> with <code>NSGlobalDomain</code>.</p>
</aside>
<p>Setting the scroll direction via the command line isn’t too bad. I just construct a <code>Process</code>, configure it to run <code>defaults</code> with the right arguments, and then launch it:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private func</span> setDirection(<span class="hl-kw">_</span> new: <span class="hl-type">Direction</span>) {
    <span class="hl-kw">let</span> proc = <span class="hl-type">Process</span>()
    proc.<span class="hl-prop">launchPath</span> = <span class="hl-str">"/usr/bin/defaults"</span>
    <span class="hl-kw">let</span> newVal = new == .<span class="hl-prop">normal</span> ? <span class="hl-str">"NO"</span> : <span class="hl-str">"YES"</span>
    proc.<span class="hl-prop">arguments</span> = [<span class="hl-str">"write"</span>, <span class="hl-str">"-g"</span>, <span class="hl-str">"com.apple.swipescrolldirection"</span>, <span class="hl-str">"-bool"</span>, newVal]
    proc.<span class="hl-fn">launch</span>()
    proc.<span class="hl-fn">waitUntilExit</span>()

    <span class="hl-kw">if</span> proc.<span class="hl-prop">terminationStatus</span> != <span class="hl-num">0</span> {
		<span class="hl-fn">fatalError</span>(<span class="hl-str">"uh oh, exit code:</span> \(proc.<span class="hl-prop">terminationStatus</span>)<span class="hl-str">"</span>)
    }
}
</code></pre>
<p>With that wired up to run when the menu item was clicked, I tried toggling the scroll direction. Unfortunately, it failed, because <code>defaults</code> didn’t run successfully. But, it had at least printed an error message:</p>
<pre class="highlight" data-lang="txt"><code>[User Defaults] Couldn't write values for keys (
    &quot;com.apple.swipescrolldirection&quot;
) in CFPrefsPlistSource&lt;0x6000017a1200&gt; (Domain: kCFPreferencesAnyApplication, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: Yes): setting preferences outside an application's container requires user-preference-write or file-write-data sandbox access
</code></pre>
<p>Everyone’s favorite sandbox, snatching defeat from the jaws of victory.</p>
<h2 id="fun-problem-#2-sandboxing"><a href="#fun-problem-#2-sandboxing" class="header-anchor" aria-hidden="true" role="presentation">##</a> Fun Problem #2: Sandboxing</h2>
<p>Unfortunately, both of the mentioned entitlements seem to be private (the only mentions of them I can find on the internet are from people running into some Catalyst error). So, I needed to disable sandboxing for this app altogether.</p>
<p>Disabling sandboxing turned out to be annoyingly confusing. Most of the documentation you can find on the internet is outdated and says you can simple flip the “App Sandbox” switch in the Capabilities section of the Xcode project. Slight problem: as of Xcode 13 (if not earlier), that switch no longer exists. And flat out removing the App Sandbox entry from Signing &amp; Capabilities did not disable it, the same error occurred. Setting App Sandbox to <code>NO</code> in the entitlements file was similarly ineffective.</p>
<p>After banging my head against this for a while (it seems like this has not been discussed on the internet recently enough to be of any help), I looked in target’s Build Settings. Where I found the “Enable App Sandbox” flag—and it was set to Yes. Setting it to No finally fixed the issue, and the default was actually getting set.</p>
<p>The updated value could successfully be read from the app itself, as well as from outside. And that left me where I got stuck before on the command line: the preference was being updated, but nothing else on the system was aware of the change.</p>
<h2 id="into-the-caves"><a href="#into-the-caves" class="header-anchor" aria-hidden="true" role="presentation">##</a> Into the Caves</h2>
<p>I knew the name of the notification I needed to fire, but not what to do with it—the normal <code>NotificationCenter</code> only works in-process, doesn’t it? I decided the best course of action was to go spelunking through the System Preferences binary to try and figure out what it was doing. But not actually the System Preferences binary: there’s a separate binary for each preference pane. A little bit of poking around the filesystem led me to the <code>/System/Library/PreferencePanes/</code> directory where all the builtin ones live. <code>Mouse.prefPane</code> looked exactly like what I wanted. Opening it in <a href="https://www.hopperapp.com/" data-link="hopperapp.com">Hopper</a>, I could search the strings for the notification name. Following the references to the string back through the CFString led me to the <code>-[MouseController awakeFromNib]</code> method.</p>
<p>Looking at the disassembly, we can see exactly what it’s doing:</p>
<div class="article-content-wide"><img src="/2021/scrollswitcher/awakefromnib.png" alt="Hopper showing the disassembly for -[MouseController awakeFromNib]"></div>
<p>It’s adding an observer to <code>NSDistributedNotificationCenter</code>’s <code>defaultCenter</code> for the <code>SwipeScrollDirectionDidChangeNotification</code>—the notification name I saw earlier in the Console. The other place it’s referenced from (<code>[MTMouseScrollGesture initWithDictionary:andReadPreferences:]</code>) is doing the same thing: adding an observer. So, it looks like this notification isn’t what triggers the actual change to the actual scroll input handling deep in the guts of the system. If that were the case, I’d expect to see the preference pane <em>send</em> the notification, not just receive it.</p>
<p>But, it still may be useful. Looking at the implementation of the <code>_swipeScrollDirectionDidChangeNotification:</code> method it’s setting as the callback for the action, we can see that it’s probably updating the checkbox value. That <code>setState:</code> call sure seems like it’s on the <code>NSButton</code> used for the natural scrolling checkbox.</p>
<div class="article-content-wide"><img src="/2021/scrollswitcher/notificationhandler.png" alt="Hopper showing the disassembly for -[MouseController _swipeScrollDirectionDidChangeNotification:]"></div>
<p><a href="https://developer.apple.com/documentation/foundation/nsdistributednotificationcenter" data-link="developer.apple.com/documentation/founda…"><code>NSDistributedNotificationCenter</code></a> is described as being like the regular notification center but for inter-process communication, which sounds like what we want. It has pretty much the same API as the regular one, so we can just send that notification when we change the scroll mode.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">Notification</span>.<span class="hl-type">Name</span> {
    <span class="hl-kw">static let</span> swipeScrollDirectionDidChangeNotification = <span class="hl-type">Notification</span>.<span class="hl-type">Name</span>(rawValue: <span class="hl-str">"SwipeScrollDirectionDidChangeNotification"</span>)
}
</code></pre><pre class="highlight" data-lang="swift"><code><span class="hl-kw">private func</span> setDirection(<span class="hl-kw">_</span> new: <span class="hl-type">Direction</span>) {
    <span class="hl-kw">let</span> proc = <span class="hl-type">Process</span>()
    <span class="hl-cmt">// omitted</span>
    <span class="hl-type">DistributedNotificationCenter</span>.<span class="hl-fn">default</span>().<span class="hl-fn">postNotificationName</span>(.<span class="hl-prop">swipeScrollDirectionDidChangeNotification</span>, object: <span class="hl-kw">nil</span>, userInfo: <span class="hl-kw">nil</span>, deliverImmediately: <span class="hl-kw">false</span>)
}
</code></pre>
<p>With that in place, clicking the menu bar item both sets the default and causes the System Preferences UI to update to match. Going the other way is similarly easy, since, although I couldn’t find it in <code>Mouse.prefPane</code>, something is emitting that notification when the value changes. I just call <code>addObserver</code> and register myself for the notification and update the icon when it’s received.</p>
<div>
	<video controls style="max-width: 100%; margin: 0 auto; display: block;" title="Screen recording of menu bar item changing highlighted state in sync with the natural scrolling checkbox in System Preferences.">
		<source src="/2021/scrollswitcher/sync.mp4" type="video/mp4">
	</video>
</div>
<h2 id="back-into-the-caves"><a href="#back-into-the-caves" class="header-anchor" aria-hidden="true" role="presentation">##</a> Back Into the Caves</h2>
<p>That’s all well and good, but clicking the menu item still doesn’t actually change what happens when you move two fingers on the trackpad. It clearly works when clicking the checkbox in System Preferences, so there must be something else it’s doing that we’re not. Internally, this feature seems to be consistently referred to as the “swipe scroll direction” (even though it affects non-swipe scrolling), so, back in Hopper, we can search for procedures named like that. There’s one that immediately looks promising <code>setSwipeScrollDirection</code>, that just delegates to an externally implemented <code>_setSwipeScrollDirection</code>.</p>
<div class="article-content-wide"><img src="/2021/scrollswitcher/setswipescrolldirection.png" alt="Hopper showing the assembly for setSwipeScrollDirection"></div>
<p>Looking at the references to the function, I saw it was called by the <code>-[MouseController scrollingBehavior:]</code> setter. That seems like the function that I wanted, but since it was implemented elsewhere, I had no idea what parameters it took. So, where’s it implemented?</p>
<p>I used <code>otool -L</code> to print all the frameworks the prefpane was linked against, and then started guessing.</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">otool</span></span> <span class="hl-const">-L</span> /System/Library/PreferencePanes/Mouse.prefPane/Contents/MacOS/Mouse
<span class="hl-fn">/System/Library/PreferencePanes/Mouse.prefPane/Contents/MacOS/Mouse:</span>
	<span class="hl-fn">/System/Library/Frameworks/PreferencePanes.framework/Versions/A/PreferencePanes (compatibility</span> version 1.0.0, current version 1.0.0)
# rest omitted
</code></pre>
<p>Actually getting the framework binaries is a bit tricky, since, starting with macOS Big Sur, the binaries are only present in the <code>dyld</code> shared cache. The process is a little bit annoying, but not terribly complicated. <a href="https://lapcatsoftware.com/articles/bigsur.html" data-link="lapcatsoftware.com/articles/bigsur.html">This article</a> by Jeff Johnson explains how to build <code>dyld_shared_cache_util</code>, which you can use to transform the shared cache back into a directory with all the framework binaries.</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">dyld_shared_cache_util</span></span> <span class="hl-const">-extract</span> ~/Desktop/Libraries/ /System/Library/dyld/dyld_shared_cache_x86_64
</code></pre>
<p>It took a couple guesses, but I found that the <code>_setSwipeScrollingDirection</code> function is defined in <code>PreferencePanesSupport.framework</code>. </p>
<div class="article-content-wide"><img src="/2021/scrollswitcher/preferencepanessupport.png" alt="Hopper showing the disassembly for _setSwipeScrollDirection in PreferencePanesSupport"></div>
<p>Hopper thinks it takes an int, but we can clearly see the parameter’s being used as a bool. <code>rcx</code> is initialized to <code>kCFBooleanFalse</code> and set to <code>kCFBooleanTrue</code> if the parameter is true, and that’s the value being passed to <code>CFPreferencesSetValue</code>. Perfect.</p>
<p>Now—finally—that should be everything we need.</p>
<p>Back in the Xcode project, I added a bridging header that defines the externally implemented function and lets me call it from Swift.</p>
<pre class="highlight" data-lang="c"><code><span class="hl-kw">#import</span> &lt;stdbool.h&gt;
<span class="hl-kw">extern</span> <span class="hl-type">void</span> <span class="hl-fn">setSwipeScrollDirection</span>(<span class="hl-type">bool</span> <span class="hl-var">direction</span>);
</code></pre>
<p>Then, from Swift, we can simply call the function.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private func</span> setDirection(<span class="hl-kw">_</span> new: <span class="hl-type">Direction</span>) {
    <span class="hl-kw">let</span> proc = <span class="hl-type">Process</span>()
    <span class="hl-cmt">// omitted</span>
    <span class="hl-type">DistributedNotificationCenter</span>.<span class="hl-fn">default</span>().<span class="hl-fn">postNotificationName</span>(.<span class="hl-prop">swipeScrollDirectionDidChangeNotification</span>, object: <span class="hl-kw">nil</span>, userInfo: <span class="hl-kw">nil</span>, deliverImmediately: <span class="hl-kw">false</span>)
    <span class="hl-fn">setSwipeScrollDirection</span>(new == .<span class="hl-prop">natural</span>)
}
</code></pre>
<p>Lastly, in order to make the extern function actually go to the right place, the app needs to be linked against <code>/System/Library/PrivateFrameworks/PreferencePanesSupport.framework</code>. And with that, clicking the menu item toggles the preference and immediately updates the user input behavior.</p>
<p>I can’t really take a screen recording of that, so you’ll have to take my word that it works.</p>
<p>If you’re interested in the complete code, it can be found <a href="https://git.shadowfacts.net/shadowfacts/ScrollSwitcher" data-link="git.shadowfacts.net/shadowfacts/ScrollSw…">here</a>. It’s not currently packaged for distribution, but you can build and run it yourself. Because it needs the sandbox disabled, it won’t ever been in the App Store, but at some point I might slap an app icon on it and published a notarized, built version. So, if anyone’s interested, let me know.</p>
<p>As it currently exists, the app—which I’m calling ScrollSwitcher—covers 90% of my needs. I don’t generally dock/undock more than a one or twice a day, so just being able to click a menu bar item is plenty fast. That said, I may still extend it for “fun”. One obvious improvement would be automatically changing the state when an external mouse is connected/disconnected. That shouldn’t be too hard, right? Right?</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Or, if the Mouse prefs pane was already opened in the current session, relaunch the System Preferences app. Preference panes are not reloaded when you close and re-enter them, so manually writing the default causes the UI to desync. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>On SwiftUI</title>
      <link>https://shadowfacts.net/2021/swiftui/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2021/swiftui/</guid>
      <pubDate>Wed, 25 Aug 2021 19:34:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Over the past several days, I built a complete, functioning app in SwiftUI, and, well, I have some thoughts.</p>
<!-- excerpt-end -->
<p>The app I built is a little TOTP app that works exactly the way I want, because Authy is bad<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. And it plays to SwiftUI’s strengths pretty well. It’s got a straightforward UI, with very few custom interface elements. The data model is simple and how it interacts with the interface is clearly delineated.</p>
<p>For the most part, writing the app has been a good experience. The vast majority of it I wrote in about 20 hours worth of work spread over four days. As of writing this, it’s about 1700 lines of Swift. The app itself is open source <a href="https://git.shadowfacts.net/shadowfacts/OTP" data-link="git.shadowfacts.net/shadowfacts/OTP">here</a>, so if you want to see the codebase that I’m rambling on about, you can take a look.</p>
<p>The process was generally enjoyable. It’s what everyone says about SwiftUI: you can iterate incredibly quickly, there’s vastly less boilerplate than UIKit, and it’s much easier to build simple, reusable views.</p>
<p>This was also the first project where I actually managed to use the Xcode Previews feature. Although it always seems slick in WWDC demos, I was never able to get it to work reliably. But this time was different. The fact that Xcode pauses previews whenever something outside of the view body changes remains annoying, but at least while I was only modifying the body it did update reasonably quickly. I guess it really just works best for smaller projects.</p>
<p>Building almost all of the UI was easy. The design is simple enough that it doesn’t have to use any fancy tricks or workarounds—not a <code>GeometryReader</code> in sight. Plumbing up the model was similarly painless, both for editing and displaying data (aside from some oddities involving timing and refreshing codes).</p>
<p>But, here’s where the complaints start. Although the layout and design were uncomplicated, building some of the interactions were not. While working on it, SwiftUI felt incredibly powerful and uncomfortably restrictive simultaneously. For example: </p>
<p>Want to make give a context menu action the destructive style? Ok. Want to make a nested menu destructive? Nope.</p>
<p>Want to drag and drop one view onto another view? Go ahead. How about dropping that view onto one that’s inside a <code>List</code>? Woah there.</p>
<p>Making a text field focused programatically? Sure. And what about making it focused immediately on appearance? Not so fast.</p>
<p>To be clear, many of the issues I’ve encountered with SwiftUI are almost certainly bugs that will (hopefully) be fixed eventually. But where this differs from UIKit is that the architecture of UIKit lets you fix and workaround things yourself, whereas SwiftUI operates as a black box.</p>
<p>To use the same examples, but in UIKit: to make a context menu element destructive or not is exactly the same regardless of whether it has children, adding a drop interaction to a UIView is the same inside a table view as it is outside, and focusing a text field uses the same method no matter when in the lifecycle it’s called.</p>
<p>I don’t think this is just because I have more experience with UIKit. It’s not that there are lots of UIKit tricks I know to make this work, in constrast with SwiftUI. There is a fundamental difference between the two frameworks. The delta between doing any of the things I mentioned in one context versus in the other is zero. Because of the fundamental design of UIKit, all of these things basically just work. Yes, implementation wise, the framework engineers may still need to take special care to ensure that they work. But, from an API design point of view, nothing different is required.</p>
<p>The impression I get from Apple is that SwiftUI wants to make hard things easy. That’s a great goal, but currently it comes at the expense of making easy things complicated. And every time I do anything substantial with SwiftUI, I constantly feel this tension. SwiftUI both empowers me and hinders me, every step of the way.</p>
<p>I don’t know what the solution is, if there is one (part of me thinks these sorts of issues are intrinsic to declarative tools), but I really hope there is one. When it works, I really enjoy SwiftUI and I want to be able to enjoy it more without running into unresolvable framework issues quite so frequently.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>It only shows one code at a time and requires too many taps to switch. It also has no way of exporting or backing up the TOTP secrets. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Debugging My Gemini NWProtocolFramer Implementation</title>
      <link>https://shadowfacts.net/2021/gemini-client-debugging/</link>
      <category>swift</category>
      <category>gemini</category>
      <guid>https://shadowfacts.net/2021/gemini-client-debugging/</guid>
      <pubDate>Thu, 08 Jul 2021 03:32:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I recently ran into an issue with the Network.framework Gemini client I’d <a href="/2020/gemini-network-framework/" data-link="/2020/gemini-network-framework/">previously implemented</a> that turned out to be somewhat perplexing. So, I thought I’d write a brief post about it in case anyone finds it interesting or helpful.</p>
<!-- excerpt-end -->
<p>The gist of the issue is that when connecting to a certain host, the connection would hang indefinitely. The issue was 100% reproducible in my app, both in the simulator and on an actual device, in all manner of network conditions. What led me to believe that it was an issue with my implementation was that the problem only happened with a single host (I suspect the incompatibility was with whatever server software was being used) and the fact that I could not reproduce the issue with any other client.</p>
<p>My initial attempts at debugging were fruitless. Every handler and callback I had was just never getting called. There was no error that was being swallowed or anything, just silence. Eventually I set a breakpoint in the <code>handleInput</code> method of my <code>NWProtocolFramerImplementation</code>.</p>
<p>Stepping through<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, I saw the status code get parsed successfully. Then it moved on to parsing the response meta, a string of up to 1024 bytes followed by a carriage return and line feed.</p>
<p>Next, I paused inside the <code>parseInput</code> closure that’s responsible for parsing the meta string. The buffer that I received was 11 bytes long. I was immediately wary because I’d seen that number before in this context—it’s the length of <code>text/gemini</code>. Indeed, printing out <code>String(bytes: buffer, encoding: .utf8)</code> revealed that it was indeed the meta string I was expecting. But it was just the meta string, not including the CR/LF pair that’s supposed to follow it.</p>
<p>Continuing to step through, I saw the closure return 0, indicating that no bytes were consumed, because the CRLF was missing. After that, the main body of the <code>handleInput</code> method would see that the meta wasn’t found and would itself return the number of bytes it expected to receive before it should be invoked again. It does this by adding 1 to the length of the buffer from which meta parsing failed. So, the next time <code>handleInput</code> is called by the framework, there should be at least 12 bytes available.</p>
<p>After hitting resume, the debugger trapped again in the <code>parseInput</code> closure for the meta. I checked <code>buffer.count</code> and found… 11 bytes. I know <code>handleInput</code> returned 12, so why did I still only have 11 bytes?</p>
<p>The realization I came to, after puzzling over this for a couple days, is that <code>parseInput</code> behaves a little weirdly, though in a way that’s technically correct. It seems that even if more data is available in some internal buffer of the framer’s—which we know there must be because <code>handleInput</code> was invoked again after we returned 12 earlier—we won’t receive all of it. It’s not <em>wrong</em>, 11 bytes is indeed at least 2 and no more than 1026, but it’s certainly unintuitive.</p>
<p>To verify this behavior, I tweaked my code to call <code>parseInput</code> with a minimum length of 13 instead of 2 the second attempt to parse meta. Lo and behold, it worked. The buffer now had 13 bytes of data: the full meta string and the CR/LF.</p>
<p>And that explains the user-facing issue that led to all this. A few attempts at parsing would fail, but then the server would stop sending data because there was nothing left, so <code>handleInput</code> would never be called, leaving the Gemini request in a waiting state forever.</p>
<p>So, to properly fix the issue what needs to happen is we have to be smarter about the <code>minimumIncompleteLength</code> on subsequent attempts to parse the metadata. To do this, I saved the length of the last buffer for which meta parsing was attempted as an instance variable in the framer implementation. With that, we can determine how many bytes we <em>actually</em> need before trying again.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private var</span> lastAttemptedMetaLength: <span class="hl-type">Int</span>? = <span class="hl-kw">nil</span>
	<span class="hl-cmt">// ...</span>

	<span class="hl-kw">func</span> handleInput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Int</span> {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">if</span> tempMeta == <span class="hl-kw">nil</span> {
			<span class="hl-kw">let</span> min: <span class="hl-type">Int</span>
			<span class="hl-kw">if let</span> lastAttemptedMetaLength = lastAttemptedMetaLength {
				min = lastAttemptedMetaLength + <span class="hl-num">1</span>
			} <span class="hl-kw">else</span> {
				min = <span class="hl-num">2</span>
			}
			<span class="hl-kw">_</span> = framer.<span class="hl-fn">parseInput</span>(minimumIncompleteLength: min, maximumLength: <span class="hl-num">1024</span> + <span class="hl-num">2</span>) { (buffer, isComplete) -&gt; <span class="hl-type">Int</span> <span class="hl-kw">in
				guard let</span> buffer = buffer <span class="hl-kw">else</span> { <span class="hl-kw">return</span> }
				<span class="hl-kw">self</span>.<span class="hl-prop">lastAttemptedMetaLength</span> = buffer.<span class="hl-prop">count</span>
				<span class="hl-cmt">// ...</span>
			}
		}
		<span class="hl-kw">guard let</span> meta = tempMeta <span class="hl-kw">else</span> {
			<span class="hl-kw">if let</span> attempted = <span class="hl-kw">self</span>.<span class="hl-prop">lastAttemptedMetaLength</span> {
				<span class="hl-kw">return</span> attempted + <span class="hl-num">1</span>
			} <span class="hl-kw">else</span> {
				<span class="hl-kw">return</span> <span class="hl-num">2</span>
			}
		}
		<span class="hl-cmt">// ...</span>
	}

	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The minimum incomplete length still defaults to 2 because on the first attempt to parse, we don’t have previous attempt info and the only thing we know is that there must be a carriage return and line feed (as best as I can interpret it, the Gemini spec doesn’t say that there must be meta text, so it could be zero bytes).</p>
<p>With that, the original issue is finally fixed and requests to the problematic server complete successfully.</p>
<p>My best guess for why this was happening in the first place and only in such specific circumstances is this: the specific server software being used was sending the meta text over the wire separately from the CRLF. This could mean they arrive at my device separately and are thus stored by in two separate “chunks”. Then, when <code>parseInput</code> is called, Network.framework simply starts looking through the stored chunks in order. And, since the first chunk is longer than <code>minimumIncompleteLength</code>, it’s the only one that’s returned.</p>
<p>There’s a note in the Network.framework header comments<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup> for <code>nw_framer_parse_input</code> that leads me to believe this. It says, with regard to the <code>temp_buffer</code> parameter:</p>
<blockquote>
<p>If it is NULL, the buffer provided in the completion will not copy unless a copy is required to provide the minimum bytes as a contiguous buffer.</p>
</blockquote>
<p>The possibility of a copy being needed to form a contiguous buffer implies that there could be discontiguous data, which lines up with my “chunks” hypothesis and would explain the behavior I observed.</p>
<aside>
<p>Fun fact, the C function corresponding to this Swift API, <code>nw_framer_parse_input</code>, takes a maximum length, but it also lets to pass in your own temporary buffer, in the form of a <code>uint8_t*</code>. It’s therefore up to the caller to ensure that the buffer that’s pointed to is at least as long as the maximum length. This seems like a place ripe for buffer overruns in sloppily written protocol framer implementations.</p>
</aside>
<p>Anyhow, if you’re interested, you can find the current version of my Gemini client implementation (as of this post) <a href="https://git.shadowfacts.net/shadowfacts/Gemini/src/commit/3055cc339fccad99ab064f2daccdb65efa8024c0/GeminiProtocol/GeminiProtocol.swift" data-link="git.shadowfacts.net/shadowfacts/Gemini/s…">here</a>.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Debugging this is kind of painful because much of the actual work is done inside the closure passed to <code>NWProtocolFramer.Instance.parseInput</code>. It’s invoked synchronously, so it does affect control flow, but if you click Step Over at the wrong time, you can accidentally skip a whole critical chunk of code. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Annoyingly it’s not present in the Swift docs nor the Objective-C docs. Seriously, this is not good. There’s a wealth of information in the header comments that’s not present in the regular docs. If you feel so inclined, you can dupe FB9163518: “Many NWProtocolFramer.Instance methods are missing docs that are present in the generated headers”. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Part 11: Lexical Scope</title>
      <link>https://shadowfacts.net/2021/lexical-scope/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/lexical-scope/</guid>
      <pubDate>Tue, 29 Jun 2021 23:14:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>After adding variables, I added boolean values and comparison operators, because why not. With that in place, I figured it would be a good time to add if statements. Parsing them is straightforward—you just look for the <code>if</code> keyword, followed by a bunch of stuff—so I won’t go into the details. But actually evaluating them was a bit more complicated.</p>
<!-- excerpt-end -->
<p>The main issue is that of lexical scope, which is where the context (i.e., what variables are accessible) at each point in the program is defined by where in the original source code it is.</p>
<p>Let’s say you have some code:</p>
<pre class="highlight" data-lang="txt"><code>let a = 1
if (condition) {
	print(a)
	let b = 2
}
print(b)
</code></pre>
<p>Entering the body of the if statement starts a new scope in which variables defined in the any encompassing scope can be accessed, but not vice versa. <code>a</code>, defined in the outer scope, can be read from the inner scope, but <code>b</code>, defined in the inner scope, cannot be accessed from the outer scope.</p>
<p>What this means for me is that, in the evaluator, it’s not just enough to have access to the current scope. All the parent scopes are also needed.</p>
<p>There are a couple ways I could approach this. One way would be to have something like a vector of contexts, where the last element is the current context. Accessing a parent context would just mean walking backwards through the vector. And to enter a new scope, you’d construct a new context and push it onto the vector, evaluate whatever you wanted in the new scope, and then remove it from the vector afterwards. This would work, but it risks needing to replace the vector’s backing storage every time a context is entered. It’s probably a premature optimization, but I decided to take a different approach to avoid the issue.</p>
<p>Another way of doing it is effectively building a singly-linked list, where each <code>Context</code> stores an optional reference to its parent. But a simple reference isn’t enough. The <code>Context</code> struct would need a generic lifetime parameter in order to know how long the reference to its parent lives for. And in order to specify what type the reference refers to, we would need to be able to know how long the parent context’s parent lives for. And in order to spell that type out we’d have to know how long the parent’s parent’s parent—you get the idea.</p>
<p>The solution I came up with was to wrap the context struct in an <code>Rc</code>, a reference-counted pointer. So, instead of each context having a direct reference to its parent, it owns an <code>Rc</code> that’s backed by the same storage. Though, that’s not quite enough, because the context needs to be mutable so code can do things like set variables. For that reason, it’s actually an <code>Rc&lt;RefCell&lt;Context&gt;&gt;</code>. I understand this pattern of interior mutability is common practice in Rust, but coming from languages where this sort of things is handled transparently, it’s one of the weirder things I’ve encountered.</p>
<p>Now, on to how this is actually implemented. It’s pretty simple. The <code>Context</code> struct stores the <code>Rc</code> I described above, but inside an <code>Option</code> so that the root context can have <code>None</code> as its parent.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">struct</span> <span class="hl-type">Context</span> {
	<span class="hl-prop">parent</span>: <span class="hl-type">Option</span>&lt;<span class="hl-type">Rc</span>&lt;<span class="hl-type">RefCell</span>&lt;<span class="hl-type">Context</span>&gt;&gt;&gt;,
	<span class="hl-prop">variables</span>: <span class="hl-type">HashMap</span>&lt;<span class="hl-type">String</span>, <span class="hl-type">Value</span>&gt;,
}
</code></pre>
<p>Then, instead of the various eval functions taking a reference directly to the context, they get a reference to the <code>Rc</code>-wrapped context instead.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval_binary_op</span>(<span class="hl-var">left</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>, <span class="hl-var">op</span>: <span class="hl-op">&amp;</span><span class="hl-type">BinaryOp</span>, <span class="hl-var">right</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>, <span class="hl-var">context</span>: <span class="hl-op">&amp;</span><span class="hl-type">Rc</span>&lt;<span class="hl-type">RefCell</span>&lt;<span class="hl-type">Context</span>&gt;&gt;) -&gt; <span class="hl-type">Value</span> {
	<span class="hl-kw">let</span> left_value = <span class="hl-fn">eval_expr</span>(left, context);
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The constructors for <code>Context</code> also change a bit. There’s one that doesn’t have a parent, called <code>root</code>, in addition to <code>new</code> which does.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">impl</span> <span class="hl-type">Context</span> {
	<span class="hl-kw">fn</span> <span class="hl-fn">root</span>() -&gt; <span class="hl-type">Self</span> {
		<span class="hl-type">Self</span> {
			<span class="hl-prop">parent</span>: None,
			<span class="hl-prop">variables</span>: <span class="hl-type">HashMap</span>::<span class="hl-fn">new</span>(),
		}
	}

	<span class="hl-kw">fn</span> <span class="hl-fn">new</span>(<span class="hl-var">parent</span>: <span class="hl-type">Rc</span>&lt;<span class="hl-type">RefCell</span>&lt;<span class="hl-type">Context</span>&gt;&gt;) -&gt; <span class="hl-type">Self</span> {
		<span class="hl-type">Self</span> {
			<span class="hl-prop">parent</span>: Some(parent),
			<span class="hl-prop">variables</span>: <span class="hl-type">HashMap</span>::<span class="hl-fn">new</span>(),
		}
	}
}
</code></pre>
<p>Unlike the evaluation functions, <code>Context::new</code> takes an owned <code>Rc</code>, avoiding the infinite-lifetimes problem from earlier. This requires that, when constructing a new context, we just need to clone the existing <code>Rc</code>.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval_if</span>(<span class="hl-var">condition</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>, <span class="hl-var">body</span>: <span class="hl-op">&amp;</span>[<span class="hl-type">Statement</span>], <span class="hl-var">context</span>: <span class="hl-op">&amp;</span><span class="hl-type">Rc</span>&lt;<span class="hl-type">RefCell</span>&lt;<span class="hl-type">Context</span>&gt;&gt;) {
	<span class="hl-kw">let</span> body_context = <span class="hl-type">Context</span>::<span class="hl-fn">new</span>(<span class="hl-type">Rc</span>::<span class="hl-fn">clone</span>(context));
	<span class="hl-kw">let</span> body_context_ref = <span class="hl-type">Rc</span>::<span class="hl-fn">new</span>(<span class="hl-type">RefCell</span>::<span class="hl-fn">new</span>(body_context));
}
</code></pre>
<p>After the new context is constructed, it too is wrapped in a <code>RefCell</code> and <code>Rc</code> for when the condition and body are evaluated. This is a little bit unweidly, but hey, it works.</p>
<p>Actually evaluating the if is simple enough that I won’t bother going through it in detail. It just evaluates the condition expression (confirming that it’s a boolean; there shall be no implicit conversions!) and, if it’s true, evaluates each of the statements in the body.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> code = <span class="hl-str">r#&quot;let a = 1; if (a == 1) { dbg(a); }&quot;#</span>;
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-op">&amp;</span>code);
	<span class="hl-kw">let</span> statements = <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens);
	<span class="hl-fn">eval</span>(<span class="hl-op">&amp;</span>statements);
}
</code></pre><pre class="highlight" data-lang="txt"><code>$ cargo run
Integer(1)
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Part 10: Variable Declarations</title>
      <link>https://shadowfacts.net/2021/variable-declarations/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/variable-declarations/</guid>
      <pubDate>Sun, 09 May 2021 23:14:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Now that the parser can handle multiple statements and the usage of variables, let’s add the ability to actually declare variables.</p>
<!-- excerpt-end -->
<p>First off, the lexer now lexes a couple new things: the <code>let</code> keyword and the equals sign.</p>
<p>When the parser tries to parse a statement and sees a let token, it knows it’s looking at a variable declaration. After the let token it expects to find a identifier (the variable name), an equals sign, and then an expression for the initial value of the variable. The variable name and the initial value expression then make up a <code>Declare</code> AST node.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_statement</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Statement</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">let</span> node = <span class="hl-kw">match</span> token {
		<span class="hl-type">Token</span>::Let =&gt; {
			<span class="hl-kw">let</span> name: <span class="hl-type">String</span>;
			<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(<span class="hl-type">Token</span>::Identifier(s)) = it.<span class="hl-fn">peek</span>() {
				name = s.<span class="hl-fn">clone</span>();
				it.<span class="hl-fn">next</span>();
			} <span class="hl-kw">else</span> {
				<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;expected identifier after let&quot;</span>);
			}
			<span class="hl-fn">expect_token</span><span class="hl-fn">!</span>(it, Equals, <span class="hl-str">&quot;expected equals after identifier after let&quot;</span>);
			<span class="hl-kw">let</span> value = <span class="hl-fn">parse_expression</span>(it).<span class="hl-fn">expect</span>(<span class="hl-str">&quot;initial value in let statement&quot;</span>);
			Some(Statement::<span class="hl-type">Declare</span> { name, value })
		}
		<span class="hl-cmt">// ...</span>
	};
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p><code>expect_token!</code> is a simple macro I wrote to handle expecting to see a specific token in the stream and panicking if it’s not there, since that’s a pattern that was coming up frequently:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">macro_rules!</span> expect_token {
	($stream:ident, $token:ident, $msg:expr) =&gt; {
		<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(Token::$token) = $stream.peek() {
			$stream.next();
		} else {
			panic!($msg);
		}
	};
}
</code></pre>
<p>Next, to actually evaluate variable declarations, the evaulator needs to have some concept of a context. Right now, every expression can be evaluated without any external state. But, when a variable is declared, we want it to be accessible later on in the code, so there needs to be somewhere to store that information.</p>
<p>For now, the only information we need is the map of variable names to their values.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">struct</span> <span class="hl-type">Context</span> {
	<span class="hl-prop">variables</span>: <span class="hl-type">HashMap</span>&lt;<span class="hl-type">String</span>, <span class="hl-type">Value</span>&gt;,
}
</code></pre>
<p>There are also a few methods for <code>Context</code>, one to construct a new context and one to declare a variable with an initial value.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">impl</span> <span class="hl-type">Context</span> {
	<span class="hl-kw">fn</span> <span class="hl-fn">new</span>() -&gt; <span class="hl-type">Self</span> { 
		<span class="hl-type">Self</span> {
			<span class="hl-prop">variables</span>: <span class="hl-type">HashMap</span>::<span class="hl-fn">new</span>(),
		}
	}

	<span class="hl-kw">fn</span> <span class="hl-fn">declare_variable</span>(<span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-builtin">self</span>, <span class="hl-var">name</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>, <span class="hl-var">value</span>: <span class="hl-type">Value</span>) {
		<span class="hl-kw">if</span> <span class="hl-builtin">self</span>.<span class="hl-prop">variables</span>.<span class="hl-fn">contains_key</span>(name) {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;cannot re-declare variable {}&quot;</span>, name);
		} <span class="hl-kw">else</span> {
			<span class="hl-builtin">self</span>.<span class="hl-prop">variables</span>.<span class="hl-fn">insert</span>(name.<span class="hl-fn">into</span>(), value);
		}
	}
}
</code></pre>
<p>Every <code>eval_</code> function has also changed to take a reference to the current context<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> and the main <code>eval</code> function creates a context before evaluating each statement.</p>
<p>With that, declaration statements can be evaluated just by calling the <code>declare_variable</code> method on the context:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval_declare_variable</span>(<span class="hl-var">name</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>, <span class="hl-var">value</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>, <span class="hl-var">context</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Context</span>) {
	<span class="hl-kw">let</span> val = <span class="hl-fn">eval_expr</span>(value, context);
	context.<span class="hl-fn">declare_variable</span>(name, val);
}
</code></pre>
<p>And we can actually set and read variables now<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> code = <span class="hl-str">&quot;let foo = 1; dbg(foo)&quot;</span>;
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-op">&amp;</span>code);
	<span class="hl-kw">let</span> statements = <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens);
	<span class="hl-fn">eval</span>(<span class="hl-op">&amp;</span>statements);
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">Integer(1</span>)
</code></pre><hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>For now a simple mutable reference is fine, because there’s only ever one context: the global one. But, in the future, this will need to be something a bit more complicated. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>The <code>dbg</code> function is a builtin I added that prints out the Rust version of the <code>Value</code> it’s passed. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Part 9: Statements</title>
      <link>https://shadowfacts.net/2021/statements/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/statements/</guid>
      <pubDate>Mon, 03 May 2021 21:46:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>So the parser can handle a single expression, but since we’re not building a Lisp, that’s not enough. It needs to handle multiple statements. For context, an expression is a piece of code that represents a value whereas a statement is a piece of code that can be executed but does not result in a value.</p>
<!-- excerpt-end -->
<p>In the AST, there’s a new top-level type: <code>Statement</code>. For now, the only type of statement is one that contains an expression and nothing else.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">Statement</span> {
	Expr(<span class="hl-type">Node</span>),
}
</code></pre>
<p>The top level <code>parse</code> function has also changed to reflect this. It now returns a vector of statements, instead of a single expression node. The <code>do_parse</code> function continues to work exactly as it has, but is renamed <code>parse_expression</code> to since that’s what it’s actually doing.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse</span>(<span class="hl-var">tokens</span>: <span class="hl-op">&amp;</span>[<span class="hl-type">Token</span>]) -&gt; <span class="hl-type">Vec</span>&lt;<span class="hl-type">Statement</span>&gt; {
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> it = tokens.<span class="hl-fn">iter</span>().<span class="hl-fn">peekable</span>();
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> statements: <span class="hl-type">Vec</span>&lt;<span class="hl-type">Statement</span>&gt; = <span class="hl-fn">vec</span><span class="hl-fn">!</span>[];
	<span class="hl-kw">while</span> <span class="hl-kw">let</span> Some(_) = it.<span class="hl-fn">peek</span>() {
		<span class="hl-kw">match</span> <span class="hl-fn">parse_statement</span>(<span class="hl-op">&amp;</span><span class="hl-kw">mut</span> it) {
			Some(statement) =&gt; statements.<span class="hl-fn">push</span>(statement),
			None =&gt; (),
		}
	}
	statements
}
</code></pre>
<p>The <code>parse_statement</code> function does exactly what the name suggests.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_statement</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Statement</span>&gt; {
	<span class="hl-kw">if</span> it.<span class="hl-fn">peek</span>().<span class="hl-fn">is_none</span>() {
		<span class="hl-kw">return</span> None;
	}

	<span class="hl-kw">let</span> node = <span class="hl-fn">parse_expression</span>(it).<span class="hl-fn">map</span>(|node| <span class="hl-type">Statement</span>::Expr(node));
	node
}
</code></pre>
<p>With that in place, parsing multiple statements is easy. The only change is that, after successfully parsing a statement, we need to consume a semicolon if there is one. Then, the <code>parse</code> loop will continue and the next statement can be parsed.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_statement</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Statement</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>() {
		Some(<span class="hl-type">Token</span>::Semicolon) =&gt; {
			it.<span class="hl-fn">next</span>();
		}
		Some(tok) =&gt; {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token {:?} after statement&quot;</span>, tok);
		}
		None =&gt; (),
	}
	
	node
}
</code></pre>
<p>I intend to make semicolons optional and allow newline-delimited statements, but that is more complicated and will have to wait for another time. For now, this is good enough:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;1 + 2; foo();&quot;</span>);
	<span class="hl-fn">print</span>(<span class="hl-str">&quot;statements: {:?}&quot;</span>, <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens));
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">statements:</span> [
	<span class="hl-fn">Expr</span>(
		<span class="hl-fn">BinaryOp</span> {
			<span class="hl-fn">left:</span> Integer(<span class="hl-fn">1</span>)<span class="hl-fn">,</span>
			<span class="hl-fn">op:</span> Add,
			<span class="hl-fn">right:</span> Integer(<span class="hl-fn">2</span>)<span class="hl-fn">,</span>
		<span class="hl-fn">},</span>
	)<span class="hl-fn">,</span>
	<span class="hl-fn">Expr</span>(
		<span class="hl-fn">Call</span> {
			<span class="hl-fn">name:</span> <span class="hl-str">&quot;foo&quot;</span>,
			<span class="hl-fn">params:</span> [],
		<span class="hl-fn">},</span>
	)<span class="hl-fn">,</span>
<span class="hl-fn">]</span>
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Part 8: Variable Lookups and Function Calls</title>
      <link>https://shadowfacts.net/2021/variable-lookups-and-function-calls/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/variable-lookups-and-function-calls/</guid>
      <pubDate>Sun, 25 Apr 2021 15:15:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Arithmetic expressions are all well and good, but they don’t really feel much like a programming language. To fix that, let’s start working on variables and function calls.</p>
<!-- excerpt-end -->
<p>First step: lexing.</p>
<p>There are two new token types: identifier and comma.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">Token</span> {
	<span class="hl-cmt">// ...</span>
	Identifier(<span class="hl-type">String</span>),
	Comma,
}
</code></pre>
<p>The comma is just a single comma character. The identifier is a sequence of characters that represent the name of a variable or function. An identifier starts with a letter (either lower or uppercase) and is followed by any number of letters, digits, and underscores.</p>
<p>The main <code>tokenize</code> function checks if it’s looking at a letter, and, if so, calls the <code>parse_identifier</code> function. <code>parse_identifier</code> simply accumulates as many valid identifier characters as there are and wraps them up in a token.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_identifier</span>&lt;<span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-builtin">char</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Token</span>&gt; {
	<span class="hl-kw">let</span> chars = <span class="hl-fn">take_while_peek</span>(it, |c| {
		LOWERCASE_LETTERS.<span class="hl-fn">contains</span>(c)
			|| UPPERCASE_LETTERS.<span class="hl-fn">contains</span>(c)
			|| DIGITS.<span class="hl-fn">contains</span>(c)
			|| <span class="hl-op">*</span>c == <span class="hl-str">'_'</span>
	});
	<span class="hl-kw">if</span> chars.<span class="hl-fn">is_empty</span>() {
		None
	} <span class="hl-kw">else</span> {
		<span class="hl-kw">let</span> s = <span class="hl-type">String</span>::<span class="hl-fn">from_iter</span>(chars);
		Some(<span class="hl-type">Token</span>::Identifier(s))
	}
}
</code></pre>
<p>The next step is parsing.</p>
<p>There are two new kinds of AST nodes: lookup nodes and function call nodes. The only data lookup nodes store is the name of the variable they refer to. Function call nodes store the nodes for their parameters, in addition to the function name.</p>
<p>When parsing an expression, an identifier token results in either a lookup or function call node, depending on whether it’s followed by a left-paren.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">do_parse</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> node: <span class="hl-type">Node</span> = <span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>().<span class="hl-fn">unwrap</span>() {
		<span class="hl-cmt">// ...</span>
		<span class="hl-type">Token</span>::Identifier(value) =&gt; {
			it.<span class="hl-fn">next</span>();
			<span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>() {
				Some(<span class="hl-type">Token</span>::LeftParen) =&gt; Node::<span class="hl-type">Call</span> {
					<span class="hl-prop">name</span>: value.<span class="hl-fn">clone</span>(),
					<span class="hl-prop">params</span>: <span class="hl-fn">parse_function_params</span>(it),
				}
				_ =&gt; Node::<span class="hl-type">Lookup</span> {
					<span class="hl-prop">name</span>: value.<span class="hl-fn">clone</span>(),
				},
			}
		}
	};
}
</code></pre>
<p>Actually parsing function parameters is left to another function. After consuming the opening parenthesis, it checks if the next token is the closing right-paren. If it is, the right-paren is consumed and an empty vector is returned for the paramters.</p>
<p>If it isn’t, the function enters a loop in which it parses a parameter expression and then expects to find either a comma or right-paren. If there’s a comma, it’s consumed and it moves on to the next iteration of the loop. If it’s a closing parenthesis, it too is consumed and then the loop is exited and the parameter list returned. Upon encountering any other token, it panics.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_function_params</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Vec</span>&lt;<span class="hl-type">Node</span>&gt; {
	it.<span class="hl-fn">next</span>(); <span class="hl-cmt">// consume left paren</span>
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(<span class="hl-type">Token</span>::RightParen) = it.<span class="hl-fn">peek</span>() {
		it.<span class="hl-fn">next</span>();
		<span class="hl-fn">vec</span><span class="hl-fn">!</span>[]
	} <span class="hl-kw">else</span> {
		<span class="hl-kw">let</span> <span class="hl-kw">mut</span> params = <span class="hl-fn">vec</span><span class="hl-fn">!</span>[];
		<span class="hl-kw">loop</span> {
			<span class="hl-kw">let</span> param_node = <span class="hl-fn">do_parse</span>(it).<span class="hl-fn">expect</span>(<span class="hl-str">&quot;function parameter&quot;</span>);
			params.<span class="hl-fn">push</span>(param_node);
			<span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>() {
				Some(<span class="hl-type">Token</span>::Comma) =&gt; {
					it.<span class="hl-fn">next</span>();
				}
				Some(<span class="hl-type">Token</span>::RightParen) =&gt; {
					it.<span class="hl-fn">next</span>();
					<span class="hl-kw">break</span>;
				}
				tok =&gt; {
					<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token {:?} after function parameter&quot;</span>, tok);
				}
			}
		}
		params
	}
}
</code></pre>
<p>And lastly, to make this work correctly, the comma token is added to the list of expression-end tokens.</p>
<p>With that, parsing function calls and variable lookups is possible:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;foo(bar)&quot;</span>);
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> node = <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens) {
		<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;{:?}&quot;</span>, node);
	}
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">Call</span> {
	<span class="hl-fn">name:</span> <span class="hl-str">&quot;foo&quot;</span>,
	<span class="hl-fn">params:</span> [
		<span class="hl-fn">Lookup</span> {
			<span class="hl-fn">name:</span> <span class="hl-str">&quot;bar&quot;</span>,
		<span class="hl-fn">},</span>
	<span class="hl-fn">],</span>
<span class="hl-fn">}</span>
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Part 7: Cleaning Up Binary Operators</title>
      <link>https://shadowfacts.net/2021/cleaning-up-binary-operators/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/cleaning-up-binary-operators/</guid>
      <pubDate>Mon, 19 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>The code from <a href="/2021/operator-precedence/" data-link="/2021/operator-precedence/">part 4</a> that checks whether a pair of binary operators should be grouped to the left or right works, but I’m not particularly happy with it. The issue is that it needs to pattern match on the right node twice: first in the <code>should_group_left</code> function, and then again in <code>combine_with_binary_operator</code> if <code>should_group_left</code> returned true.</p>
<!-- excerpt-end -->
<p>As a reminder, the code currently looks like this:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">combine_with_binary_operator</span>(<span class="hl-var">left</span>: <span class="hl-type">Node</span>, <span class="hl-var">token</span>: <span class="hl-op">&amp;</span><span class="hl-type">Token</span>, <span class="hl-var">right</span>: <span class="hl-type">Node</span>) -&gt; <span class="hl-type">Node</span> {
	<span class="hl-kw">let</span> op: <span class="hl-type">BinaryOp</span> = <span class="hl-kw">match</span> token {
		<span class="hl-cmt">// ...</span>
	};

	<span class="hl-kw">if</span> <span class="hl-fn">should_group_left</span>(<span class="hl-op">&amp;</span>op, <span class="hl-op">&amp;</span>right) {
		<span class="hl-kw">if</span> <span class="hl-kw">let</span> Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: right_left,
			<span class="hl-prop">op</span>: right_op,
			<span class="hl-prop">right</span>: right_right,
		} {
			Node::<span class="hl-type">BinaryOp</span> {
				<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(Node::<span class="hl-type">BinaryOp</span> {
					<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
					op,
					<span class="hl-prop">right</span>: right_left,
				}),
				<span class="hl-prop">op</span>: right_op,
				<span class="hl-prop">right</span>: right_right,
			}
		} <span class="hl-kw">else</span> {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>();
		}
	} <span class="hl-kw">else</span> {
		Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
			op,
			<span class="hl-prop">right</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(right),
		}
	}
}

<span class="hl-kw">fn</span> <span class="hl-fn">should_group_left</span>(<span class="hl-var">left_op</span>: <span class="hl-op">&amp;</span><span class="hl-type">BinaryOp</span>, <span class="hl-var">right</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>) -&gt; <span class="hl-builtin">bool</span> {
	<span class="hl-kw">match</span> right {
		Node::<span class="hl-type">BinaryOp</span> { <span class="hl-prop">op</span>: right_op, .. } =&gt; {
			right_op.<span class="hl-fn">precedence</span>() &lt; left_op.<span class="hl-fn">precedence</span>()
				|| (right_op.<span class="hl-fn">precedence</span>() == left_op.<span class="hl-fn">precedence</span>()
					&amp;&amp; left_op.<span class="hl-fn">associativity</span>() == <span class="hl-type">Associativity</span>::Left)
		}
		_ =&gt; <span class="hl-const">false</span>,
	}
}
</code></pre>
<p>See that <code>panic!()</code> in the else branch? The compiler thinks it (or some return value) is necessary there, because the pattern match could fail. But as the programmer, I know better. I know that if we’re in the true branch of the outer if statement, then <code>should_group_left</code> returned true and the pattern match can never fail.</p>
<p>This is why I just call <code>panic!</code> without even a message: because I know that code is unreachable.</p>
<p>But it would be even better not to have it at all.</p>
<p>Basically, what I want the <code>should_group_left</code> function to do is pattern match on the right node, and if it meets the conditions for being left-grouped, to get the values inside the right binary operator node out without having to do another pattern match.</p>
<aside class="inline">
<p>Swift handles this rather nicely, because it allows you to combine multiple if… clauses? together with commas, including mixing and matching boolean conditions and pattern matching, requiring them all to succeed for the body of the if to be executed.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">if</span> <span class="hl-fn">should_group_left</span>(op, right),
	case <span class="hl-kw">let</span> .<span class="hl-fn">binaryOp</span>(rightLeft, rightOp, rightRight) = <span class="hl-fn">right</span> {
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>Other languages with flow typing, like TypeScript or Kotlin, handle similar issues with things like custom <a href="https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards" data-link="typescriptlang.org/docs/handbook/advance…">type guards</a>, which inform the compiler “if this function returns true, the following type constraint holds”.</p>
</aside>
<p>The best solution I was able to come up with was changing <code>should_group_left</code> to take ownership of the right node and return a <code>Result&lt;(Box&lt;Node&gt;, BinaryOp, Box&lt;Node&gt;), Node&gt;</code><sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. If it returns an Ok result, all the values are available. If it returns an “error”, ownership of the right node is returned back to the caller.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">should_group_left</span>(
	<span class="hl-var">left_op</span>: <span class="hl-op">&amp;</span><span class="hl-type">BinaryOp</span>,
	<span class="hl-var">right</span>: <span class="hl-type">Node</span>,
) -&gt; <span class="hl-type">Result</span>&lt;(<span class="hl-type">Box</span>&lt;<span class="hl-type">Node</span>&gt;, <span class="hl-type">BinaryOp</span>, <span class="hl-type">Box</span>&lt;<span class="hl-type">Node</span>&gt;), <span class="hl-type">Node</span>&gt; {
	<span class="hl-kw">match</span> right {
		Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: right_left,
			<span class="hl-prop">op</span>: right_op,
			<span class="hl-prop">right</span>: right_right,
		} =&gt; {
			<span class="hl-kw">let</span> should_group = <span class="hl-cmt">// ...</span>
			<span class="hl-kw">if</span> should_group {
				Ok((right_left, right_op, right_right))
			} <span class="hl-kw">else</span> {
				Err(Node::<span class="hl-type">BinaryOp</span> {
					<span class="hl-prop">left</span>: right_left,
					<span class="hl-prop">op</span>: right_op,
					<span class="hl-prop">right</span>: right_right,
				})
			}
		}
		_ =&gt; Err(right),
	}
}
</code></pre>
<p>Even this isn’t ideal, because in the else branch in the first match arm, I still need to reconstruct the original <code>right</code> node, since it’s been moved by the destructuring. I spent a while playing around with using references for various things in this function, but ultimately couldn’t come up with anything better than this. If you have any ideas, let me know.</p>
<p>At any rate, at the call site in <code>combine_with_binary_operator</code>, this works pretty well:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">combine_with_binary_operator</span>(<span class="hl-var">left</span>: <span class="hl-type">Node</span>, <span class="hl-var">token</span>: <span class="hl-op">&amp;</span><span class="hl-type">Token</span>, <span class="hl-var">right</span>: <span class="hl-type">Node</span>) -&gt; <span class="hl-type">Node</span> {
	<span class="hl-kw">let</span> op = <span class="hl-kw">match</span> token {
		<span class="hl-cmt">// ...</span>
	};

	<span class="hl-kw">match</span> <span class="hl-fn">should_group_left</span>(<span class="hl-op">&amp;</span>op, right) {
		Ok((right_left, right_op, right_right)) =&gt; Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(Node::<span class="hl-type">BinaryOp</span> {
				<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
				op,
				<span class="hl-prop">right</span>: right_left,
			}),
			<span class="hl-prop">op</span>: right_op,
			<span class="hl-prop">right</span>: right_right,
		},
		Err(right) =&gt; Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
			op,
			<span class="hl-prop">right</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(right),
		},
	}
}
</code></pre><hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>It doesn’t really need to be a <code>Result</code> specifically, I just didn’t bother writing my own enum just for this. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Part 6: Grouping</title>
      <link>https://shadowfacts.net/2021/grouping/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/grouping/</guid>
      <pubDate>Sun, 18 Apr 2021 18:42:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Parsing groups is pretty straightforward, with only one minor pain point to keep in mind. I’ll gloss over adding left and right parentheses because it’s super easy—just another single character token.</p>
<!-- excerpt-end -->
<p>To actually parse the group from the token stream, in the <code>parse_expression</code> function I look for a left paren at the beginning of an expression, and call <code>parse_group</code> if one is found.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_expression</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> node: <span class="hl-type">Node</span> = <span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>().<span class="hl-fn">unwrap</span>() {
		<span class="hl-cmt">// ...</span>
		<span class="hl-type">Token</span>::LeftParen =&gt; <span class="hl-fn">parse_group</span>(it).<span class="hl-fn">unwrap</span>(),
	}
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>The <code>parse_group</code> function is also pretty simple. It consumes the left paren and then calls <code>parse_expression</code> to parse what’s inside the parentheses. Afterwards, assuming it’s found something, it consumes the right paren and returns a new <code>Group</code> node (which has just one field, another boxed <code>Node</code> that’s its content.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_group</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>() {
		Some(<span class="hl-type">Token</span>::LeftParen) =&gt; (),
		_ =&gt; <span class="hl-kw">return</span> None,
	}

	it.<span class="hl-fn">next</span>();

	<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(inner) = <span class="hl-fn">parse_expression</span>(it) {
		<span class="hl-kw">match</span> it.<span class="hl-fn">peek</span>() {
			Some(<span class="hl-type">Token</span>::RightParen) =&gt; (),
			tok =&gt; <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;expected closing parenthesis after group, got {:?}&quot;</span>, tok),
		}
		it.<span class="hl-fn">next</span>();
		Some(Node::<span class="hl-type">Group</span> {
			<span class="hl-prop">node</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(inner),
		})
	} <span class="hl-kw">else</span> {
		<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;expected expression inside group&quot;</span>);
	}
}
</code></pre>
<p>This looks pretty good, but trying to run it and parse an expression like <code>(1)</code> will crash the program. Specifically, it’ll panic with a message saying <code>unexpected token: RightParen</code>.</p>
<p>At first, this was pretty confusing. Shouldn’t the right paren be consumed the <code>parse_group</code> function? Running with <code>RUST_BACKTRACE=1</code> reveals what the problem actually is.</p>
<p>It’s panicking inside the recursive call to <code>parse_expression</code> coming from <code>parse_group</code>, before that function even has a chance to cosume the right paren. Specifically, <code>parse_expression</code> is seeing a token after the first part of the expression and is trying to combine it with the existing node and failing because a right paren is not a binary operator.</p>
<p>What should happen is that <code>parse_expression</code> should see the paren following the expression, realize that the expression is over, and not do anything with it. That way, when the recursive <code>parse_expression</code> returns, <code>parse_group</code> will be able to consume the right paren as it expects.</p>
<p>To do this, there’s a constant list of tokens which are considered to end the current expression. Then, in <code>parse_expression</code>, in addition to checking if the next token after an expression is a binary operator, we can check if the token is an expression end. And if so, avoid panicking.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">const</span> EXPRESSION_END_TOKENS: <span class="hl-op">&amp;</span>[<span class="hl-type">Token</span>] = <span class="hl-op">&amp;</span>[<span class="hl-type">Token</span>::RightParen];

<span class="hl-kw">fn</span> <span class="hl-fn">parse_expression</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">T</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(next) = it.<span class="hl-fn">peek</span>() {
		<span class="hl-kw">if</span> <span class="hl-fn">is_binary_operator_token</span>(next) {
			<span class="hl-cmt">// ...</span>
		} <span class="hl-kw">else</span> <span class="hl-kw">if</span> EXPRESSION_END_TOKENS.<span class="hl-fn">contains</span>(next) {
			<span class="hl-cmt">// no-op</span>
		} <span class="hl-kw">else</span> {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token: {:?}&quot;</span>, next);
		}
	}

	Some(node)
}
</code></pre>
<p>And now it can parse grouped expressions:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;(1)&quot;</span>);
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> node = <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens) {
		<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;node: {:#?}&quot;</span>, &amp;node);
	}
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">node:</span> Group {
	<span class="hl-fn">node:</span> Integer(<span class="hl-fn">1</span>)<span class="hl-fn">,</span>
<span class="hl-fn">}</span>
</code></pre>
<p>(I won’t bother discussing evaluating groups because it’s trivial.)</p>
]]></content:encoded>
    </item>
    <item>
      <title>Part 5: Fixing Floats</title>
      <link>https://shadowfacts.net/2021/fixing-floats/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/fixing-floats/</guid>
      <pubDate>Sat, 17 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>In the process of adding floating point numbers, I ran into something a little bit unexpected. The issue turned out to be pretty simple, but I thought it was worth mentioning.</p>
<!-- excerpt-end -->
<p>My initial attempt at this was a simple modification to the original <code>parse_number</code> function from the lexer. Instead of stopping when it encounters a non-digit character, I changed it to continuing collecting characters when it encounters a decimal point for the first time.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_number</span>&lt;<span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-builtin">char</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">T</span>) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Token</span>&gt; {
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> found_decimal_point = <span class="hl-const">false</span>;
	<span class="hl-kw">let</span> digits = it.<span class="hl-fn">take_while</span>(|c| {
		<span class="hl-kw">if</span> DIGITS.<span class="hl-fn">contains</span>(c) {
			<span class="hl-const">true</span>
		} <span class="hl-kw">else</span> <span class="hl-kw">if</span> <span class="hl-op">*</span>c == <span class="hl-str">'.'</span> &amp;&amp; !found_decimal_point {
			found_decimal_point = <span class="hl-const">true</span>;
			<span class="hl-const">true</span>
		} <span class="hl-kw">else</span> {
			<span class="hl-const">false</span>
		}
	});
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>This seemed to work, and produced tokens like <code>Float(1.2)</code> for the input “1.2”. But then I tried it with the string “1.2.3”, to make sure that lexing would fail when it encountered a dot after the number literal. But it didn’t. It failed because it didn’t expect the ‘3’ character. The dot seemed to vanish into thin air.</p>
<p>I came up with a simpler test case<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> is = <span class="hl-fn">vec</span><span class="hl-fn">!</span>[<span class="hl-const">1</span>, <span class="hl-const">2</span>, <span class="hl-const">3</span>, <span class="hl-const">4</span>];
	<span class="hl-kw">let</span> it = is.<span class="hl-fn">iter</span>().<span class="hl-fn">peekable</span>();
	<span class="hl-kw">let</span> taken = <span class="hl-fn">take_some</span>(it);
	<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;taken: {:?}, next: {:?}&quot;</span>, taken, it.peek());
}

<span class="hl-kw">fn</span> <span class="hl-fn">take_some</span>&lt;<span class="hl-type">I</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-builtin">i32</span>&gt;&gt;(<span class="hl-op">&amp;</span><span class="hl-kw">mut</span> it: <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;) -&gt; <span class="hl-type">Vec</span>&lt;<span class="hl-builtin">i32</span>&gt; {
	it.<span class="hl-fn">take_while</span>(|i| <span class="hl-op">*</span><span class="hl-op">*</span>i &lt; <span class="hl-const">3</span>).<span class="hl-fn">collect</span>()
}
</code></pre>
<p>To my surprise, it printed <code>taken: [1, 2], next: Some(4)</code>. This time the <code>3</code> disappeared.</p>
<p>I inquired about this behavior on the fediverse, and learned that I missed a key line of the docs for the <code>take_while</code> method. Before it invokes the closure you passed in, it calls <code>next()</code> on the underlying iterator in order to actually have an item for the closure to test. So, it ends up consuming the first element for which the closure returns <code>false</code>.</p>
<p>I would have expected it to use the <code>peek()</code> method on peekable iterators to avoid this, but I guess not. No matter, a peeking version is easy to implement:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">take_while_peek</span>&lt;<span class="hl-type">I</span>, <span class="hl-type">P</span>&gt;(<span class="hl-var">peekable</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">I</span>&gt;, <span class="hl-kw">mut</span> <span class="hl-var">predicate</span>: <span class="hl-type">P</span>) -&gt; <span class="hl-type">Vec</span>&lt;I::<span class="hl-type">Item</span>&gt; 
<span class="hl-kw">where</span>
	<span class="hl-type">I</span>: <span class="hl-type">Iterator</span>,
	<span class="hl-type">P</span>: <span class="hl-type">FnMut</span>(<span class="hl-op">&amp;</span>I::<span class="hl-type">Item</span>) -&gt; <span class="hl-builtin">bool</span>,
{
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> vec: <span class="hl-type">Vec</span>&lt;I::<span class="hl-type">Item</span>&gt; = <span class="hl-fn">vec</span><span class="hl-fn">!</span>[];
	<span class="hl-kw">while</span> <span class="hl-kw">let</span> Some(it) = peekable.<span class="hl-fn">peek</span>() {
		<span class="hl-kw">if</span> <span class="hl-fn">predicate</span>(it) {
			vec.<span class="hl-fn">push</span>(peekable.<span class="hl-fn">next</span>().<span class="hl-fn">unwrap</span>());
		} <span class="hl-kw">else</span> {
			<span class="hl-kw">break</span>;
		}
	}
	vec
}
</code></pre>
<p>I can then switch to using the new function in <code>parse_number_literal</code> and it no longer consumes extra characters.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_number</span>&lt;<span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-builtin">char</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">T</span>) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Token</span>&gt; {
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> found_decimal_point = <span class="hl-const">false</span>;
	<span class="hl-kw">let</span> digits = <span class="hl-fn">take_while_peek</span>(it, |c| {
		<span class="hl-cmt">// ...</span>
	});
	<span class="hl-cmt">// ...</span>
}
</code></pre><hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I’m not entirely sure why the <code>take_some</code> function is needed here. Trying to call <code>take_while</code> directly from <code>main</code> causes a compiler error on the next line at the call to <code>it.peek()</code> because the iterator is being used after being moved into <code>take_while</code>. Does having a separate function somehow fix this? I wouldn’t think so, but I’m not a Rust expert. I <a href="https://social.shadowfacts.net/notice/A6FitupF6BiJmFEwim" data-link="social.shadowfacts.net/notice/A6FitupF6B…">posted about it</a> on the fediverse, and if you have an answer, I’d love to hear it. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Part 4: Operator Precedence</title>
      <link>https://shadowfacts.net/2021/operator-precedence/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/operator-precedence/</guid>
      <pubDate>Fri, 16 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’ve gone through the lexer, parser, and evaluator and added subtraction, multiplication, and division in addition to, uh… addition. And they kind of work, but there’s one glaring issue that I mentioned back in part 2. It’s that the parser has no understanding of operator precedence. That is to say, it doesn’t know which operators have a higher priority in the order of operations when implicit grouping is taking place.</p>
<!-- excerpt-end -->
<p>Currently, an expression like <code>2 * 3 + 4</code> will be parsed as if the <code>3 + 4</code> was grouped together, meaning the evaluation would ultimately result in 14 instead of the expected 10. This is what the AST for that expression currently looks like:</p>
<pre class="highlight" data-lang="plaintext"><code>  *
 / \
2   +
   / \
  3   4
</code></pre>
<p>But, the multiplication operator should actually have a higher pririty and therefore be deeper in the node tree so that it’s evaluated first.</p>
<p>Another closely related issue is <a href="https://en.wikipedia.org/wiki/Operator_associativity" data-link="en.wikipedia.org/wiki/Operator_associati…">associativity</a>. Whereas operator precedence governs implicit grouping behavior when there are operators of <em>different</em> precedences (like addition and multiplication), operator associativity defines how implicit grouping works for multiple operators <em>of the same precedence</em> (or multiple of the same operator).</p>
<p>Looking at the AST for an expression like “1 - 2 - 3”, you can see the same issue is present as above:</p>
<pre class="highlight" data-lang="plaintext"><code>  -
 / \
1   -
   / \
  2   3
</code></pre>
<p>In both of these cases, what the parser needs to do is the same. It needs to implicitly group the middle node with the left node, rather than the right one. This will result in node trees that look like this:</p>
<pre class="highlight" data-lang="plaintext"><code>    +           -
   / \         / \
  *   4       -   3
 / \         / \
2   3       1   2
</code></pre>
<p>To accomplish this, I added precedence and associativity enums as well as methods on the <code>BinaryOp</code> enum to get each operation’s specific values so that when the parser is parsing, it can make a decision about how to group things based on this information.</p>
<p>The <code>Precedence</code> enum has a derived implementation of the <code>PartialOrd</code> trait, meaning the cases are ordered from least to greatest in the same order they’re written in the code, so that the precedence values can be compared directly with operators like <code>&lt;</code>. Addition/subtraction and multiplication/division currently share precedences. Also, every operator currently has left associativity.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">BinaryOp</span> {
	Add,
	Subtract,
	Multiply,
	Divide,
}
<span class="hl-attr">#[derive(PartialEq, PartialOrd)]</span>
<span class="hl-kw">enum</span> <span class="hl-type">Precedence</span> {
	AddSub,
	MulDiv
}
<span class="hl-attr">#[derive(PartialEq)]</span>
<span class="hl-kw">enum</span> <span class="hl-type">Associativity</span> {
	Left,
	Right,
}
<span class="hl-kw">impl</span> <span class="hl-type">BinaryOp</span> {
	<span class="hl-kw">fn</span> <span class="hl-fn">precedence</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>) -&gt; <span class="hl-type">Precedence</span> {
		<span class="hl-kw">match</span> <span class="hl-builtin">self</span> {
			<span class="hl-type">BinaryOp</span>::Add | <span class="hl-type">BinaryOp</span>::Subtract =&gt; <span class="hl-type">Precedence</span>::AddSub,
			<span class="hl-type">BinaryOp</span>::Multiply | <span class="hl-type">BinaryOp</span>::Divide =&gt; <span class="hl-type">Precedence</span>::MulDiv,
		}
	}
	<span class="hl-kw">fn</span> <span class="hl-fn">associativity</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>) -&gt; <span class="hl-type">Associativity</span> {
		<span class="hl-type">Associativity</span>::Left
	}
}
</code></pre>
<p>In the <code>do_parse</code> function, things have been changed up. First, there’s a separate function for checking if the token that follows the first token in the expression should combine with the first node (i.e., is a binary operator):</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">is_binary_operator_token</span>(<span class="hl-var">token</span>: <span class="hl-op">&amp;</span><span class="hl-type">Token</span>) -&gt; <span class="hl-builtin">bool</span> {
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> <span class="hl-type">Token</span>::Plus | <span class="hl-type">Token</span>::Minus | <span class="hl-type">Token</span>::Asterisk | <span class="hl-type">Token</span>::Slash = token {
		<span class="hl-const">true</span>
	} <span class="hl-kw">else</span> {
		<span class="hl-const">false</span>
	}
}
</code></pre>
<p>So instead of matching on individual tokens, <code>do_parse</code> just calls that function. If the next token is a binary operator, it consumes the operator token, calls <code>do_parse</code> recursively to get the right-hand node and then calls another function to combine the two nodes.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">do_parse</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">Peekable</span>&lt;<span class="hl-type">T</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(next) = it.<span class="hl-fn">peek</span>() {
		<span class="hl-kw">if</span> <span class="hl-fn">is_binary_operator_token</span>(next) {
			<span class="hl-kw">let</span> operator_token = it.<span class="hl-fn">next</span>().<span class="hl-fn">unwrap</span>();
			<span class="hl-kw">let</span> right = <span class="hl-fn">do_parse</span>(it).<span class="hl-fn">expect</span>(<span class="hl-str">&quot;expression after binary operator&quot;</span>);
			node = <span class="hl-fn">combine_with_binary_operator</span>(node, operator_token, right);
		} <span class="hl-kw">else</span> {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token: {:?}&quot;</span>, next);
		}
	}

	Some(node)
}
</code></pre>
<p>But, before I get to the <code>combine_with_binary_operator</code> function, there’s another function that decides whether a binary operator should be grouped to the left with another node by following the rule I described earlier.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">should_group_left</span>(<span class="hl-var">left_op</span>: <span class="hl-op">&amp;</span><span class="hl-type">BinaryOp</span>, <span class="hl-var">right</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>) -&gt; <span class="hl-builtin">bool</span> {
	<span class="hl-kw">match</span> right {
		Node::<span class="hl-type">BinaryOp</span> { <span class="hl-prop">op</span>: right_op, .. } =&gt; {
			right_op.<span class="hl-fn">precedence</span>() &lt; left_op.<span class="hl-fn">precedence</span>()
				|| (right_op.<span class="hl-fn">precedence</span>() == left_op.<span class="hl-fn">precedence</span>()
					&amp;&amp; left_op.<span class="hl-fn">associativity</span>() == <span class="hl-type">Associativity</span>::Left)
		}
		_ =&gt; <span class="hl-const">false</span>,
	}
}
</code></pre>
<p>The <code>combine_with_binary_operator</code> function can then use this (after converting the binary operator token into a <code>BinaryOp</code> value) to decide what it should do.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">combine_with_binary_operator</span>(<span class="hl-var">left</span>: <span class="hl-type">Node</span>, <span class="hl-var">token</span>: <span class="hl-op">&amp;</span><span class="hl-type">Token</span>, <span class="hl-var">right</span>: <span class="hl-type">Node</span>) -&gt; <span class="hl-type">Node</span> {
	<span class="hl-kw">let</span> op: <span class="hl-type">BinaryOp</span> = <span class="hl-kw">match</span> token {
		<span class="hl-cmt">// ...</span>
	};

	<span class="hl-kw">if</span> <span class="hl-fn">should_group_left</span>(<span class="hl-op">&amp;</span>op, <span class="hl-op">&amp;</span>right) {
		<span class="hl-kw">if</span> <span class="hl-kw">let</span> Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: right_left,
			<span class="hl-prop">op</span>: right_op,
			<span class="hl-prop">right</span>: right_right,
		} {
			Node::<span class="hl-type">BinaryOp</span> {
				<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(Node::<span class="hl-type">BinaryOp</span> {
					<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
					op,
					<span class="hl-prop">right</span>: right_left,
				}),
				<span class="hl-prop">op</span>: right_op,
				<span class="hl-prop">right</span>: right_right,
			}
		} <span class="hl-kw">else</span> {
			<span class="hl-fn">panic</span><span class="hl-fn">!</span>();
		}
	} <span class="hl-kw">else</span> {
		Node::<span class="hl-type">BinaryOp</span> {
			<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(left),
			op,
			<span class="hl-prop">right</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(right),
		}
	}
}
</code></pre>
<p>If there are two binary operators and it does need to be grouped to the left, it performs the same transformation I described above, constructing a new outer binary operator node and the new left-hand inner node. Diagramatically, the transformation looks like this (where uppercase letters are the binary operator nodes and lowercase letters are values):</p>
<pre class="highlight" data-lang="plaintext"><code>Original expression: x A y B z

  A               B
 / \             / \
x   B     -&gt;    A   z
   / \         / \
  y   z       x   y
</code></pre>
<p>If it does not need to be grouped left, the function simply creates a new binary operator node, leaving the left and right sides as-is.</p>
<p>And, after adding the new operators to the <code>eval_binary_op</code> function, it can now <strong>correctly</strong> compute simple arithmetic expressions!</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;2 * 3 + 4&quot;</span>);
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> node = <span class="hl-fn">parse</span>(<span class="hl-op">&amp;</span>tokens) {
		<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;result: &quot;</span>, eval(&amp;node));
	}
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">result:</span> Integer(<span class="hl-fn">10</span>)
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Part 3: Basic Evaluation</title>
      <link>https://shadowfacts.net/2021/evaluation/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/evaluation/</guid>
      <pubDate>Thu, 15 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Last time I said operator precedence was going to be next. Well, if you’ve read the title, you know that’s not the case. I decided I really wanted to see this actually run<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> some code<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>, so let’s do that.</p>
<!-- excerpt-end -->
<p>First, there needs to be something to actually store values during the evaluation process. For this, I used yet another enum. It only has one case for now because we can currently only lex and parse integer values and one arithmetic operator.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">Value</span> {
	Integer(<span class="hl-builtin">i64</span>),
}
</code></pre>
<p>There’s also a helper function to extract the underlying integer from a value in places where we’re certain it’s going to be an integer:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">impl</span> <span class="hl-type">Value</span> {
	<span class="hl-kw">fn</span> <span class="hl-fn">expect_integer</span>(<span class="hl-op">&amp;</span><span class="hl-builtin">self</span>, <span class="hl-op">&amp;</span><span class="hl-type">msg</span>) -&gt; <span class="hl-builtin">i64</span> {
		<span class="hl-kw">match</span> <span class="hl-builtin">self</span> {
			<span class="hl-type">Value</span>::Integer(n) =&gt; <span class="hl-op">*</span>n,
			_ =&gt; <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;{}&quot;</span>, msg),
		}
	}
}
</code></pre>
<p>The compiler warns about the unreachable match arm, but it’ll be useful once there are more types of values. (Once again, actual error reporting will wait.)</p>
<p>The actual evaulation starts in the <code>eval</code> function which takes a reference to the node to evaluate and returns a <code>Value</code> representing its result.</p>
<p>For integer nodes, the value of the AST node is wrapped in a Value and returned directly. For binary operator (i.e. addition) nodes the left- and right-hand values are extracted and another function is called to perform the operation.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval</span>(<span class="hl-var">node</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>) -&gt; <span class="hl-type">Value</span> {
	<span class="hl-kw">match</span> node {
		<span class="hl-type">Node</span>::Integer(n) =&gt; <span class="hl-type">Value</span>::Integer(<span class="hl-op">*</span>n),
		Node::<span class="hl-type">BinaryOp</span> { left, right } =&gt; <span class="hl-fn">eval_binary_op</span>(left, right),
	}
}
</code></pre>
<p>This <code>eval_binary_op</code> function takes each of the nodes and calls <code>eval</code> with it. By doing this, it recurses through the the AST evaluating each node in a depth-first manner. It then turns each value into an integer (panicking if either isn’t what it expects) and returns a new Value with the values added together.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">eval_binary_op</span>(<span class="hl-var">left</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>, <span class="hl-var">right</span>: <span class="hl-op">&amp;</span><span class="hl-type">Node</span>) -&gt; <span class="hl-type">Value</span> {
	<span class="hl-kw">let</span> left = <span class="hl-fn">eval</span>(left).<span class="hl-fn">expect_integer</span>(<span class="hl-str">&quot;left hand side of binary operator must be an integer&quot;</span>);
	<span class="hl-kw">let</span> right = <span class="hl-fn">eval</span>(right).<span class="hl-fn">expect_integer</span>(<span class="hl-str">&quot;right hand side of binary operator must be an integer&quot;</span>);
	<span class="hl-type">Value</span>::Integer(left + right)
}
</code></pre>
<p>And with that surpisingly small amount of code, I’ve got a very dumb calculator that can perform arbitrary additions: </p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;1 + 2 + 3&quot;</span>);
	<span class="hl-kw">if</span> <span class="hl-kw">let</span> Some(node) = <span class="hl-fn">parse</span>(tokens) {
		<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;result: {:?}&quot;</span>, eval(&amp;node));
	}
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">result:</span> Integer(<span class="hl-fn">6</span>)
</code></pre>
<p>Next time, I’ll add some more operators and actually get around to operator precedence.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>evaluate <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>a single expression <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Part 2: A Primitive Parser</title>
      <link>https://shadowfacts.net/2021/parsing/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/parsing/</guid>
      <pubDate>Wed, 14 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Now that the lexer is actually lexing, we can start parsing. This is where the Tree in Abstract Syntax Tree really comes in. What the parser is going to do is take a flat sequence of tokens and transform it into a shape that represents the actual structure of the code.</p>
<!-- excerpt-end -->
<p>The actual nodes in the AST are represented as an enum with a different case for each node type. The individual node types could also be structs that share a common trait, but so far the enum works fine.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">enum</span> <span class="hl-type">Node</span> {
	Integer(<span class="hl-builtin">i64</span>),
	BinaryOp {
		<span class="hl-prop">left</span>: <span class="hl-type">Box</span>&lt;<span class="hl-type">Node</span>&gt;,
		<span class="hl-prop">right</span>: <span class="hl-type">Box</span>&lt;<span class="hl-type">Node</span>&gt;,
	},
}
</code></pre>
<p>The <code>BinaryOp</code> node doesn’t have an operator type for now because the only supported one is addition, but it’ll be added in the future. Also, the operands of the binary operator are both boxed (stored on the heap) because otherwise the <code>Node</code> type would be recursive and its size not knowable at compile-time.</p>
<p>There’s a helper function that takes a slice of tokens and calls another function that does all of the actual work of parsing.</p>
<p>The parser is a simple recursive descent parser, so the function that does the actual parsing mutably borrows (for the same reasons as the lexer) an iterator that iterates over tokens. The actual iterator it receives is a peekable one, with the underlying iterator type specified with a generic.</p>
<p>I haven’t the faintest idea why the lifetime is needed here, because I still haven’t read that chapter of the book. But hey, adding it fixed the compiler error and it seems to work fine.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">do_parse</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> std::iter::<span class="hl-type">Peekable</span>&lt;<span class="hl-type">T</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
}
</code></pre>
<p>The <code>do_parse</code> function first ensures that there is a node (otherwise it returns <code>None</code>) and then constructs the appropriate node based on the type of the token. For now, that means just integer literals. If there’s any other type of token, it panics because it can’t be turned into a node (again, actual error reporting will come later).</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">do_parse</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> std::iter::<span class="hl-type">Peekable</span>&lt;<span class="hl-type">T</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-kw">if</span> it.<span class="hl-fn">peek</span>().<span class="hl-fn">is_none</span>() {
		<span class="hl-kw">return</span> None;
	}

	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> node: <span class="hl-type">Node</span> = <span class="hl-kw">match</span> it.<span class="hl-fn">next</span>().<span class="hl-fn">unwrap</span>() {
		<span class="hl-type">Token</span>::Integer(n) =&gt; <span class="hl-type">Node</span>::Integer(n),
		tok =&gt; <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token: {:?}&quot;</span>, tok),
	};
}
</code></pre>
<p>After that, it checks if there are any tokens remaining. If there aren’t, the node is returned as-is.</p>
<p>If there is another token, and it’s a plus sign, it creates a new binary operator node, making the previously-created <code>node</code> to the left operand. To get the right operand, it calls <code>do_parse</code> (putting the recursion into recursive descent) to create a node from the remaining tokens (if it doesn’t find a right-hand side token, it simply panics). If the next token is of any other type, it’s not able to combine it with the existing node into a new node, so it panics.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">do_parse</span>&lt;<span class="hl-op">'</span>a, <span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-op">&amp;</span><span class="hl-op">'</span>a <span class="hl-type">Token</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> std::iter::<span class="hl-type">Peekable</span>&lt;<span class="hl-type">T</span>&gt;) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-type">Node</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">match</span> it.<span class="hl-fn">next</span>() {
		Some(<span class="hl-type">Token</span>::Plus) =&gt; {
			node = Node::<span class="hl-type">BinaryOp</span> {
				<span class="hl-prop">left</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(node),
				<span class="hl-prop">right</span>: <span class="hl-type">Box</span>::<span class="hl-fn">new</span>(<span class="hl-fn">do_parse</span>(it).<span class="hl-fn">expect</span>(<span class="hl-str">&quot;expression after binary operator&quot;</span>)),
			}
		}
		Some(tok) =&gt; <span class="hl-fn">panic</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;unexpected token: {:?}&quot;</span>, tok),
		None =&gt; (),
	}

	Some(node)
}
</code></pre>
<p>And with that, it can parse a simple expression!</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-kw">let</span> tokens = <span class="hl-fn">tokenize</span>(<span class="hl-str">&quot;12 + 34 + 56&quot;</span>);
	<span class="hl-kw">let</span> node = <span class="hl-fn">parse</span>(tokens);
	<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;node: {:#?}&quot;</span>, node);
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">node:</span> Some(
    <span class="hl-fn">BinaryOp</span> {
        <span class="hl-fn">left:</span> Integer(<span class="hl-fn">12</span>)<span class="hl-fn">,</span>
        <span class="hl-fn">right:</span> BinaryOp {
            <span class="hl-fn">left:</span> Integer(<span class="hl-fn">34</span>)<span class="hl-fn">,</span>
            <span class="hl-fn">right:</span> Integer(<span class="hl-fn">56</span>)<span class="hl-fn">,</span>
        <span class="hl-fn">},</span>
    <span class="hl-fn">},</span>
)
</code></pre>
<p>The eagle-eyed may notice that while we have parsed the expression, we have not parsed it correctly. What’s missing is operator precedence and associativity, but that will have to wait for next time.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Part 1: Lexing</title>
      <link>https://shadowfacts.net/2021/lexing/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/lexing/</guid>
      <pubDate>Tue, 13 Apr 2021 21:00:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>The first part of the language I’ve built is the lexer. It takes the program text as input and produces a vector of tokens. Tokens are the individual units that the parser will work with, rather than it having to work directly with characters. A token could be a bunch of different things. It could be a literal value (like a number or string), or it could be an identifier, or a specific symbol (like a plus sign).</p>
<!-- excerpt-end -->
<p>I’ve decided to represent tokens themselves as an enum because there are a bunch of cases without any data (e.g., a plus sign is always just a plus sign) and some with (e.g., a number literal token has a value).</p>
<p>When I was reading the <a href="https://doc.rust-lang.org/stable/book/" data-link="doc.rust-lang.org/stable/book/">Rust book</a>, I was excited to see Rust enums have associated values. Enums with associated data is one of my favorite features of Swift, and I’m glad to see Rust has them too.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-attr">#[derive(Debug)]</span>
<span class="hl-kw">enum</span> <span class="hl-type">Token</span> {
	Integer(<span class="hl-builtin">u64</span>),
	Plus,
}
</code></pre>
<p>For now, I’m only starting with integer literals and the plus sign. More tokens will come once I’ve got basic lexing and parsing in place.</p>
<p>Most of the work of turning a string into tokens is done in the <em>drumroll please…</em> <code>tokenize</code> function.</p>
<p>It creates an initially empty vector of tokens, and starts iterating through characters in the string.</p>
<p>Single character tokens like plus are the easiest to handle. If the current character matches that of a token, consume the character (by advancing the iterator) and add the appropriate token.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">tokenize</span>(<span class="hl-var">s</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>) -&gt; <span class="hl-type">Vec</span>&lt;<span class="hl-type">Token</span>&gt; {
	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> tokens: <span class="hl-type">Vec</span>&lt;<span class="hl-type">Token</span>&gt; = <span class="hl-type">Vec</span>::<span class="hl-fn">new</span>();

	<span class="hl-kw">let</span> <span class="hl-kw">mut</span> it = s.<span class="hl-fn">chars</span>().<span class="hl-fn">peekable</span>();
	<span class="hl-kw">while</span> <span class="hl-kw">let</span> Some(c) = it.<span class="hl-fn">peek</span>() {
		<span class="hl-kw">if</span> <span class="hl-op">*</span>c == <span class="hl-str">'+'</span> {
			it.<span class="hl-fn">next</span>();
			tokens.<span class="hl-fn">push</span>(<span class="hl-type">Token</span>::Plus);
		}
	}

	tokens
}
</code></pre>
<p>Already I’ve encountered a Rust thing. Inside the while loop, <code>c</code> is a reference to a char, so in order to check its value, you have to dereference it. I had expected that you’d be able to compare a value of some type to a reference of that same type (with the language deref’ing the reference automatically), but I can see how forcing the programmer to be explicit about it makes sense.</p>
<p>Next, to parse numbers literals, I check if the current character is in the digit range:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">const</span> DIGITS: <span class="hl-type">RangeInclusive</span>&lt;<span class="hl-builtin">char</span>&gt; = <span class="hl-str">'0'</span>..=<span class="hl-str">'9'</span>;

<span class="hl-kw">fn</span> <span class="hl-fn">tokenize</span>(<span class="hl-var">s</span>: <span class="hl-op">&amp;</span><span class="hl-builtin">str</span>) -&gt; <span class="hl-type">Vec</span>&lt;<span class="hl-type">Token</span>&gt; {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">while</span> <span class="hl-kw">let</span> Some(c) = it.<span class="hl-fn">peek</span>() {
		<span class="hl-cmt">// ...</span>
		} else <span class="hl-kw">if</span> DIGITS.<span class="hl-fn">contains</span>(c) {
			<span class="hl-kw">let</span> n = <span class="hl-fn">parse_number</span>(<span class="hl-op">&amp;</span><span class="hl-kw">mut</span> it).<span class="hl-fn">unwrap</span>();
			tokens.<span class="hl-fn">push</span>(<span class="hl-type">Token</span>::Integer(n));
		}
	}
}
</code></pre>
<p>You may note that even though the integer token takes a signed integer, I’m totally ignoring the possibility of negative number literals. That’s because they’ll be implemented in the parser along with the unary minus operator.</p>
<p>If the character is indeed a digit, a separate function is called to parse the entire number. This is the first thing that I’ve encountered for which mutable borrows are quite nice. The <code>parse_number</code> function operates on the same data as the <code>tokenize</code> function, it needs to start wherever <code>tokenize</code> left off, and it needs to tell <code>tokenize</code> how much it advanced by. Mutably borrowing the iterator has exactly these properties.</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">parse_number</span>&lt;<span class="hl-type">T</span>: <span class="hl-type">Iterator</span>&lt;<span class="hl-type">Item</span> = <span class="hl-builtin">char</span>&gt;&gt;(<span class="hl-var">it</span>: <span class="hl-op">&amp;</span><span class="hl-kw">mut</span> <span class="hl-type">T</span>) -&gt; <span class="hl-type">Option</span>&lt;<span class="hl-builtin">i64</span>&gt; {
	<span class="hl-kw">let</span> digits = it.<span class="hl-fn">take_while</span>(|c| DIGITS.<span class="hl-fn">contains</span>(c));
	<span class="hl-kw">let</span> s: <span class="hl-type">String</span> = digits.<span class="hl-fn">collect</span>();
	s.<span class="hl-fn">parse</span>().<span class="hl-fn">ok</span>()
}
</code></pre>
<p>Writing the declaration of <code>parse_number</code> was a bit rough, I’ll admit. I haven’t read the generics chapter of the book yet, so I stumbled through a number of compiler error messages (which are very detailed!) until I arrived at this. Having emerged victorious, what I ended up with makes sense though. The specific type of the iterator doesn’t matter, but it still needs to be known at the call site during compilation.</p>
<p>The actual implementation takes as many digits as there are from the iterator, turns them into a string, and parses it into an integer. It returns an optional because the <code>parse</code> method could fail if the string there were no digit characters at the beginning of the string (this will never happen in the one case I’m calling it, but in case it’s reused in the future and I forget…).</p>
<p>Lastly, the <code>tokenize</code> function also ignores whitespace just by calling <code>it.next()</code> whenever it encounters a whitespace char.</p>
<p>And with that, we can tokenize simple inputs:</p>
<pre class="highlight" data-lang="rust"><code><span class="hl-kw">fn</span> <span class="hl-fn">main</span>() {
	<span class="hl-fn">println</span><span class="hl-fn">!</span>(<span class="hl-str">&quot;tokens: {:#?}&quot;</span>, tokenize(<span class="hl-str">&quot;12 + 34&quot;</span>));
}
</code></pre><pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> run
<span class="hl-fn">tokens:</span> [Integer(<span class="hl-fn">12</span>)<span class="hl-fn">,</span> Plus, Integer(<span class="hl-fn">34</span>)]
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Let&apos;s Build a Programming Language: Part 0</title>
      <link>https://shadowfacts.net/2021/lets-build-a-programming-language/</link>
      <category>build a programming language</category>
      <category>rust</category>
      <guid>https://shadowfacts.net/2021/lets-build-a-programming-language/</guid>
      <pubDate>Mon, 12 Apr 2021 21:27:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’ve been learning <a href="https://www.rust-lang.org/" data-link="rust-lang.org">Rust</a> for a little while now, and I’m a firm believer that the best way to learn a new language is to actually build something with it. Not something like an example project from a book or tutorial for the language, but something non-trivial that you actually find interesting.</p>
<p>In that vein, I’ve been thinking a lot lately about a building a small programming language. It’s something that I find interesting, and Rust is a good fit for it. It’s also something that lets me start out relatively small and simple, but still always have opportunities for growing more complex. </p>
<p>This post, and the posts that follow it are going to be a sort of experiment. Ordinarily, with an ongoing project, I would just post about it on the fediverse. This time, I’m going to try to write blog posts about it. I expect I’ll continue to post in-the-moment stuff on the fediverse, but I also want to write more semi-regular posts about how it’s going overall. They probably won’t be super detailed, and they certainly won’t be tutorials, but they will be more detailed and more explanatory than much of what I post on the fediverse. And, as with what I would otherwise write on the fediverse, these posts aren’t going to be perfect. I’m not an expert at this; there are going to be mistakes, and I’m going to end up backtracking. But I figure that’s slightly more interesting to read about than if I did everything correctly on the first try.</p>
<!-- excerpt-end -->
<p>With that, a few thoughts about the programming language I intend to build:</p>
<p>The biggest thing is that I don’t have a super comprehensive plan. I have some general ideas about what I want to do (and how to do it), but mostly I’m just going to see what happens. I don’t really need to use the end product for anything, I just want to have some fun building it.</p>
<p>The language is going to be interpreted. Building a compiler from scratch is not something I’m interested in (for now), and writing just a frontend for, say, LLVM, doesn’t seem terribly interesting either.</p>
<p>It will be weakly typed, because building a good, sound type system is more complicated than I want to deal with. There may be some static typing features, but it won’t be a focus.</p>
<p>It’s going to be mostly imperative. I’ll probably build more functional-language features at some point, but it’s going to be imperative first.</p>
<p>I’m also going to write everything by hand. No parser generators or combinator libraries. On a related note, there isn’t going to be a formal grammar or spec. The definition of the language will be whatever the parser does.</p>
<p>And with that, here goes nothing.</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">cargo</span></span> new toylang
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>The Intricate Image Caching Architecture of Tusker</title>
      <link>https://shadowfacts.net/2021/image-caching/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2021/image-caching/</guid>
      <pubDate>Thu, 08 Apr 2021 22:25:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>A fairly important part of Tusker (my iOS Mastodon app) is displaying images. And a bunch of varieties of images: user avatars, post attachments, custom emojis, user profile headers, as well as a few other types of rarely-shown images that get lumped in with attachments. And displaying lots of images in a performant way means caching. Lots of caching.</p>
<p>In the beginning, there was nothing. Then I started displaying images and almost immediately realized there would need to be some amount of caching. Otherwise, just scrolling down a little bit and then back up would result in images being re-loaded that were present mere seconds ago.</p>
<p>The very first implementation was super simple. It was basically just a dictionary of image URLs to the <code>Data</code> for the image at that URL. This fulfilled the primary goals of being 1) super easy to build and 2) mostly working for the simplest of use cases. But, the blog post doesn’t end here, so clearly there are some issues remaining.</p>
<!-- excerpt-end -->
<aside class="inline">
<p>Before I get to the pitfalls of the first version, let me explain a couple properties of Mastodon that make some of my caching strategies viable at all.</p>
<p>When Mastodon (and Pleroma) give you API responses, any referenced images are provided as URLs. These are URLs that point directly to the files, not to some intermediary endpoint<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. The URL corresponds to exactly one file, and which file it corresponds to will never change. When, for example, a user uploads a new avatar, the avatar URL returned from the various API requests changes.</p>
<p>This property means that I can have very long expiry times on the cache. The image data at a certain URL won’t change (unless it’s deleted), so I can just keep it for as long as I need to. And when a user changes their avatar, a new URL will be used that isn’t in the cache, so it will have to be fetched.</p>
</aside>
<p>Now, back to the implementation. The first strategy has an obvious issue: memory usage will grow indefinitely. Luckily, there’s a builtin solution for this: <a href="https://developer.apple.com/documentation/foundation/nscache" data-link="developer.apple.com/documentation/founda…">NSCache</a>. NSCache is essentially a dictionary that works with the OS to automatically remove its contents when the system needs memory for something else.</p>
<p>This worked well enough for a little while, but there’s another fairly low-hanging optimization. Because URLs aren’t reused, images can be cached for a very long time. Even across app launches, if the cache were persisted to disk. Enter the <a href="https://github.com/hyperoslo/Cache" data-link="github.com/hyperoslo/Cache">Cache</a> library. It provides memory- and disk-based caches (the memory one just wraps NSCache). While needing to load things from disk is relatively rare (because once an object is loaded from the on-disk cache, it will be kept in the in-memory cache), it’s still a nice improvement during app launch and for the eventuality that Tusker is asked by the system to give back some memory.</p>
<p>This setup served me fairly well, and (aside from bugfixes) the image caching architecture went untouched for a while. Until I started worked on improving the app’s behavior in degraded network conditions.</p>
<p>When running with the Network Link Conditioner in a super low-bandwidth preset, I launched the app to see what would happen. After a few API requests, all the posts loaded. But none of the images yet (I had purged the on-disk cache in order to test this scenario). Then the user avatars started loading in, one by one. Even for the same user.</p>
<p>The next optimization, then, is obvious. Why many request when few do trick? So, whenever something needs to load an image, instead of only checking if the URL already exists in the cache, I can also check whether there are any in-flight requests for that URL. If there are, then instead of starting a new request, the completion handler just gets tacked on to the existing request. With this in place, when you launch the app under poor network conditions, every instance of a specific user’s avatar will load in simultaneously with the net outcome being that the app overall is finished loading sooner.</p>
<p>The network request batching mechanism also has one more feature. When something calls it to either kickoff a network request or add a completion handler to one that’s already running, it receives back an object (called <code>Request</code> in my code, because that’s what they are from the API consumer’s point-of-view) which can be used to cancel the request. This is so that, if, say, a table view cell is reused, the requests for the cell’s old data can be cancelled. But because the actual network requests are batched together, calling the cancel method on the request object doesn’t necessarily cancel the underlying request (what I call a <code>RequestGroup</code>). The individual completion handler for the “cancelled” request will be removed, but the actual URL request won’t be cancelled if there are still other active handlers.</p>
<p>There’s also one more feature of the batching system. In some cases (primarily <a href="https://developer.apple.com/documentation/uikit/uitableviewdatasourceprefetching" data-link="developer.apple.com/documentation/uikit/…">table view prefetching</a>) it’s useful to pre-warm the cache. This can either be by just loading something from disk, or by starting a network request for the image (either the request will finish by the time the data is needed, in which case the image will be in the in-memory cache or it will still be in-progress, in which case the completion handler that actually wants the data will be added to the request group). For this, there are also completion handler-less requests. They are also part of the RequestGroup and contribute to keeping the underlying network request alive. Cancelling a callback-less request is trivial because, without the completion handler, each one that belongs to the same URL is identical.</p>
<p>And this was how caching worked in Tusker for almost a year and a half. But, of course, this couldn’t last forever. A few months ago, I was doing a bunch of profiling and optimizing to try to improve scroll view performance and reduce animation hitches.</p>
<p>The first thing I noticed was that while I was just scrolling through the timeline, there was a lot of time being spent in syscalls <em>in the main thread</em>. The syscalls were open, stat, and fstat and they were being called from <code>NSURL</code>’s <code>initFileURLWithPath:</code> initializer. This method was being called with the cache key (which in my case was the URL to the remote image) in order to check if the key string has a file extension so that the extension can be used for the locally cached file. It was being called very frequently because in order to check if an image exists in the disk cache, it needs to check if there’s a file on-disk at the path derived from the cache key, which includes the potential file extension of the key.</p>
<p>Another thing the <code>initFileURLWithPath:</code> initializer does is, if the path does not end with a slash, determine if it represents a directory by querying the filesystem. Since that initializer was also used to construct the final path to the cached file on-disk, it was doing even more pointless work. Because the cache is the only thing writing to that directory and all it’s writing are files, it should never need to ask the filesystem.</p>
<p>There were a couple super low-hanging optimizations here:</p>
<p>First was using NSString’s <code>pathExtension</code> property instead of turning the cache key into an NSURL to get the same property. The NSString property merely <em>interprets</em> the string as a file path, rather than hitting the disk, so it can be much faster.</p>
<p>The second thing was, as the documentation suggests, using the <code>initFileURLWithPath:isDirectory:</code> initializer instead. It allows you to specify yourself whether the path is to a directory or not, bypassing the filesystem query.</p>
<p>I sent both of these improvements upstream, because they were super simple and resulted in a nice performance improvement for free. But, while I was waiting for my changes to be merged, I came up with another optimization. This one was complex enough (though still not very) that I didn’t feel like sending it upstream, so I finally decided to just write my own copy of the library<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup> so I could make whatever changes I wanted.</p>
<p>To avoid having to do disk I/O just to check if something is cached, I added a dictionary of cache keys to file states. The file state is an enum with three cases: exists, does not exist, and unknown. When the disk cache is created, the file state dictionary is empty, so the state for every key is effectively unknown. With this, when the disk cache is asked whether there is an object for a certain key, it can first consult its internal dictionary. If file state is exists or does not exist, then no filesystem query takes place. If the state is unknown, it asks the OS whether the file exists and saves the result to the dictionary, so the request can be avoided next time. The methods for adding to/removing from the cache can then also update the dictionary to avoid potential future filesystem queries.</p>
<p>Combined with the improvements I’d sent to the upstream library, this eliminated almost all of the syscalls from the scrolling hot path. Sadly though, scrolling performance, while better, still wasn’t what I had hoped.</p>
<p>The next thing I realized was that I was being incredibly ineffecient with how images were decoded from raw data.</p>
<p>This <a href="https://developer.apple.com/videos/play/wwdc2018/219/" data-link="developer.apple.com/videos/play/wwdc2018…">WWDC session</a> from 2018 explains that although UIImage looks like a fairly simple model object, there’s more going on under the covers that can work to our advantage, if we let it.</p>
<p>The UIImage instance itself is what owns the decoded bitmap of the image. So when a UIImage is used multiple times, the PNG/JPEG/etc. only needs to be decoded once.</p>
<p>But, in both the memory and disk caches, I was only storing the data that came back from the network request. This meant that every time something needed to display an image, it would have to re-decode it from the original format into a bitmap the system could display directly. This showed up in profiles of the app as a bunch of time being spent in the ImageIO functions being called by internal UIKit code.</p>
<p>To fix this, I changed the in-memory cache to store only UIImage objects<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>, which only decode the original data once and share a single bitmap across every usage. The first time an image is retrieved from the network (or loaded from disk), a UIImage is constructed for it and stored in the memory cache. This resulted in a significant performance improvement. When running on an iPhone 6s (the device I use for performance testing), scrolling felt noticeably smoother. Additionally, this has the very nice added benefit of reducing memory consumption by a good deal.</p>
<p>We can still go one step farther with caching image objects, though. The aforementioned WWDC talk also mentions that the size of the bitmap stored internally by each UIImage is proportional to the dimensions of the input image, not to the size of the view it’s being displayed in. This is because if the same image is shown in multiple views of different sizes, it wants to retain as much information as possible so the image looks as good as it can. Another key effect of using larger-than-necessary images is that the render server needs to do more work to scale down those images to the actual display size. By doing that ourselves, ahead of time, we can keep it from repeatedly doing extra work.</p>
<p>This strategy is a reasonable default, but we, the app developer, know better. Depending on the category of image, it may only be shown at one particular size. In my case, user avatars are almost always shown at a resolution no larger than 50pt × 50pt. So, instead of keeping a bunch of full size bitmaps around, when creating the image that’s going to be cached in-memory, we can use CoreGraphics to downscale the input image to a maximum dimension of 50 points<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>. And, because the original image data is still cached on disk, if the user goes to a screen in the app where user avatars are displayed larger than usual, we can just load the original data. This is relatively uncommon compared to just scrolling through the timeline, so the slight performance hit here is a worthwhile tradeoff for the improvement in the more common case.</p>
<p>Before we reach the end, there’s one final bit of image caching Tusker does. Some time last year, I added an accessibility/digital wellness preference which changes the app to only display images in grayscale. I use the CoreImage framework to actually do this conversion<sup class="footnote-reference" id="fnref5"><a href="#5">[5]</a></sup>. CoreImage is GPU-accelerated and so is reasonably speedy, but it still adds a not-insignificant amount of time, which can be felt on slower devices. To try and mitigate this, the images are also cached post-grayscale conversion.</p>
<p>And that finally brings us to how image caching in Tusker works today. It started out very simple, and the underlying concepts largely haven’t changed, there’s just been a steady series of improvements. As with most things related to caching, what seemed initially to be a simple problem got progressively more and more complex. And, though there are a lot of moving parts, the system overall works quite well. Images are no longer the bottleneck in scrolling performance, except in the rarest of cases (like using grayscale images on the oldest supported devices. Either of those individually are fine, but together they’re just too much). And, memory usage overall is substantially reduced making the app a better platform citizen.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Unfortunately, this property is not true of Honk; the avatar URL for a Honk user looks like <code>https://example.com/a?a=&lt;USER ID&gt;</code>. Though at the time I was first building this image caching system, Honk didn’t even exist. And even today, it doesn’t really cause a problem. Honk users almost always have avatars that are procedurally generated by the software. Therefore my assumption is still largely true. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Don’t worry, it’s under the MIT license. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>Mostly. Unlike other categories of images, post attachments are not cached on disk, only in memory. This is because, generally speaking, users won’t see the same attachment often enough that it’s worth caching them across app launches. It would just be throwing away disk space for no benefit.<br>But the original need does need to be available, because constructing a UIImage from an animated GIF throws away all but the first frame. So, for attachments specifically, the original data is kept in memory. (Another obvious optimization here would be to only store the original data for GIFs in memory, and discard it for other attachments. I intend to do this eventually, I just haven’t gotten around to it as of the time I’m writing this.) <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>CoreGraphics technically wants a <em>pixel</em> size, so we multiply 50 by <code>UIScreen.main.scale</code> and use that as the max pixel dimension. This could become a minor problem on Catalyst, where screens with different scales are possible (though I don’t know how macOS display scales map to the Catalyst version of UIKit…), or if Apple added proper multi-display support to iPadOS. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div><div id="5" class="footnote-item"><span class="footnote-marker">5.</span>
<p>In an ideal world, this could be done with something like a fragment shader at render-time, but I couldn’t find any reasonable way of doing that. Oh well. <a href="#fnref5" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Twitter and Game Design Techniques</title>
      <link>https://shadowfacts.net/2021/twitter-game-design/</link>
      <category>social media</category>
      <guid>https://shadowfacts.net/2021/twitter-game-design/</guid>
      <pubDate>Fri, 26 Feb 2021 02:46:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>A few weeks ago, I read <a href="https://twitter.com/kchironis/status/1355585411943260162" data-link="twitter.com/kchironis/status/13555854119…">a thread</a> on Twitter by a game designer at Riot Games. The thread is about the concept of “celebration” in the world of game design. It refers to the techniques used to reinforce “good” behavior in video games (good not in any objective sense, but simply whatever is desired by the game designer). It was an interesting thread, but I moved on shortly after reading it and didn’t give it much more thought. Until last week when I was reading <a href="https://craigwritescode.medium.com/user-engagement-is-code-for-addiction-a2f50d36d7ac" data-link="craigwritescode.medium.com/user-engageme…">an article</a> about how social media is designed to be addictive. With the thread still floating around in the back of my mind, what I realized while thinking about the design of social media platforms was that Twitter uses the exact same techniques as video games.</p>
<!-- excerpt-end -->
<p>I’ll use Twitter as an example, because I’m very familiar with it. At a high level, what does Twitter want you to do? It wants you to keep using Twitter. Why? So it can show you more ads. But, the platform needs some way of figuring out what ads to show you, because being able to target types of ads at people who are more likely to be interested lets Twitter charge higher prices to advertisers. Twitter doesn’t just want you to be spending time on the platform, it wants you to interact and be <em>engaged</em>.</p>
<p>The most common interaction people have when just browsing Twitter is clicking the Like button. Clicking the Like button also serves as a useful signal to Twitter’s ad targeting algorithms that you probably have some interest in the topics of whichever tweet you liked.</p>
<p>So, Twitter has an action it wants you to perform. But it doesn’t want to tell you to do it, just for you to get into the habit of taking the action in the regular course of using the platform. The same problem game designers are faced with. And Twitter uses the same techniques.</p>
<p>As part of the design of a video game, you need to get the player to do certain things that will lead them to progress through the game. But, you don’t want to just throw a wall of text in their face to explain everything in great detail, you want to be more subtle about it. So, you design the game so that eventually the player will try the thing you want them to do. Then, when the player does the Good Thing, you signal your affirmation and say, “Yes, good job!” But you have to be subtle about it. You want the game to really <em>feel</em> fun for the player, not to give the impression that it’s coddling them. Instead, you use little celebratory cues that the player will perceive without even thinking about. These could be little animations, particle effects, screen shakes, or even auditory cues. There are lots of possibilities, but the key component is that celebrations don’t have to be thought about by the user.</p>
<p>When you click the Like button on a tweet, a few things happen: the heart button itself turns solid red, a small particle effect plays and the number of likes rolls up. The same techniques game designers use. The animation is eye-catching without being distracting and the like count increasing lets you subconsciously connect the action you just took to the effect it had.</p>
<p>I can’t know if Twitter does it with the deliberate intent of making users form habits, but I can’t help but feel like that is a consequence, even if a small one.</p>
]]></content:encoded>
    </item>
    <item>
      <title>M1 Mac mini Review</title>
      <link>https://shadowfacts.net/2021/m1/</link>
      <category>computers</category>
      <guid>https://shadowfacts.net/2021/m1/</guid>
      <pubDate>Thu, 14 Jan 2021 01:43:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’ve had an M1 Mac mini for about a month now, so I thought I’d write up my experiences with it. The configuration I got has 16GB of RAM and 256GB of storage (the base amount). The reasoning for the bare minimum storage is that Apple charges an arm and a leg for extra space, and I intend this to primarily be a stopgap machine until there are higher-power Apple Silicon equipped laptops. If I really desperately need extra space down the line, I can buy a cheap external SSD (that will continue to be useful after this computer is gone). The 16GB of RAM, however, is necessary to do any iOS development (Xcode and all the various associated services can by themselves easily consume almost 8 gigs). So far, I’ve moved just about all of my non-work desktop computing over to it, and it’s been absolutely fantastic.</p>
<!-- excerpt-end -->
<p>The first thing I did after setting up my account was to install Firefox. When the M1 Macs first became available there weren’t yet native versions of big applications like Firefox and Chrome available, but fortunately by the time I got mine, a beta version of Firefox that was recompiled for ARM was available.</p>
<p>Just playing around, visiting a few websites, I was happy to see everything appeared to be working. You might think this would be a given, but when I tried Firefox on the macOS 11 at the beginning of the beta cycle (on my Intel machine, even), video playback was almost completely non-functional.</p>
<p>With Firefox reinstalled, I started signing back into a bunch of my accounts. With long, random passwords, this is rather tedious, so before proceeding I wanted to install my password manager. For reasons that don’t bear getting into, I use an old version of 1Password 6, not the most recent. Of course, this means that it hasn’t been recompiled to be a Universal app; it only runs on Intel. Fortunately, Rosetta comes to the rescue. After copying the .app from another computer, I double clicked it to launch it, just like any other app, after which the OS presented a dialog asking if I wanted to install Rosetta.</p>
<p>Presumably it’s shipped separately and not bundled with the base OS because of the bandwidth requirements of shipping an x86_64 version of every system library that not everyone may need. Additional copies of system libraries are needed because within a single process only one architecture can be used: either everything must be Intel or everything must be Apple Silicon.</p>
<p>Using Rosetta for 1Password worked perfectly. Aside from a slight delay when launching the app for the first time (due to Rosetta pre-translating the Intel binary into ARM). Once it launched, everything worked exactly as I expect. Even IPC between 1Password under Rosetta and the natively-running Firefox extension works flawlessly.</p>
<p>Other Intel-only apps I’ve tried have also worked similarly well. IINA, a video player built on top of mpv, works perfectly. Inkscape, in my brief testing, also works as well as it does on Intel, which is to say, it functions but is not particularly pleasant (fortunately Inkscape some time ago released a native macOS version that no longer depends on XQuartz, removing a potential barrier). I’m also currently running MacVim under Rosetta (in which I’m writing this post) and it’s working without issue. coc.nvim is a Vim plugin which hosts Node.js extensions that provide autocompletion and LSP support. The part of the plugin that runs inside Vim runs, of course, through Rosetta. Node, which I installed through Homebrew, supports ARM natively, and, as with 1Password, IPC accross the architecture boundary works perfectly. The only other significant app I tested under Rosetta is the game Sayonara Wild Hearts. The version in the App Store is Intel-only, and it’s built with Unity 2018.4.7f1, which was released in August of 2019. It works flawlessly running at 1440p @ 144Hz. I can’t say the exact framerate, because the game doesn’t have a counter built in and I’m too lazy to find a standalone macOS one, but it’s definitely well above 60FPS and runs very smoothly, without any noticable input latency or hitching.</p>
<p>The only performance issue I encountered with Rosetta was using an older version of Node.js that was not compiled to run natively. Performance when running under Rosetta was significantly worse than both running natively on the M1 and natively on my Intel laptop. I tried to run <code>npm install</code> but called it quits after almost 5 minutes and switched to a newer version of Node that was compiled for ARM. Presumably this significant performance difference is due to Node’s usage of the V8 VM, which does just-in-time compilation. This requires Rosetta to constantly re-translate x86_64 code into ARM, rather than being able to translate everything ahead-of-time and then execute natively, without interruption.</p>
<p>Of the Mac apps I’ve tried, the sole app that hasn’t worked is MonitorControl, an app that uses the Display Data Channel to control the backlight brightness on monitors that macOS itself does not support. Although the app is running under Rosetta, that isn’t the problem. It seems that the system APIs used to access the DDC either aren’t implemented on ARM or aren’t supported by the driver for Apple’s own GPU. Hopefully this is merely an oversight that can be fixed in a future macOS update.</p>
<p>One of the first things I did upon receiving the machine was following <a href="https://soffes.blog/homebrew-on-apple-silicon" data-link="soffes.blog/homebrew-on-apple-silicon">these</a> popular instructions to install Homebrew. I installed two copies of it: one running natively on ARM, installed in <code>/opt/homebrew/</code>, and the other forced to run in x86_64 under Rosetta in the classic location. There’s also a shell alias to let me easily access the Rosetta version by running <code>ibrew</code> which wraps the Homebrew command in <code>arch -x86_64</code>.</p>
<p>I expected the native version of Homebrew to be fairly unstable, and that I would have to fallback on the Intel version frequently. In a pleasant surprise, that has not been the case. Almost everything I’ve tried to install with Homebrew has worked perfectly with the ARM version, even those packages which themselves are x86 only and run under Rosetta. In fact, the only thing that hasn’t worked with native Homebrew has been iPerf.</p>
<pre class="highlight" data-lang="sh"><code><span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">brew</span></span> list <span class="hl-const">--formula</span> <span class="hl-op">|</span> <span class="hl-fn">wc</span> <span class="hl-const">-l</span>
	<span class="hl-fn">87</span>
<span class="hl-fn"><span class="hl-op">$</span> <span class="hl-prop">ibrew</span></span> list <span class="hl-const">--formula</span> <span class="hl-op">|</span> <span class="hl-fn">wc</span> <span class="hl-const">-l</span>
	<span class="hl-fn">1</span>
</code></pre>
<p>Up to this point, everything I’d done had been not so different from setting up any previous Mac. The first thing I did that really gave me a sense of how much faster the M1 is was installing Xcode. The non-App Store version of Xcode is distributed in an ~11GB .xip file, which is a compressed, cryptographically-signed format that is notoriously slow to extract. I didn’t time it, but when extracting Xcode on my Intel laptop, I’d start the extraction process and then go back to doing something else for 10 to 15 minutes. On the M1, it took between 5 and 10 minutes. Still not terribly quick, but a substantial improvement. With Xcode installed, I was eager to start playing around with my iOS projects.</p>
<p>Tusker, my Mastodon app for iOS, is easily the biggest iOS project I have, at about 21 thousand lines of Swift code and a couple dozen .xib files (which surprisingly are the source of a significant fraction of the compile time). On my Intel laptop, which is an 8-core 16“ MacBook Pro (with 32GB of RAM, double that of the mini), a clean build in debug mode takes 47.9 seconds. On the Mac mini, the same takes 29.1 seconds, an incredible 39% improvement. When performing a full archive of the app, which builds it in release mode with all optimizations enabled, my Intel machine takes 83.9 seconds and the M1 takes 59.9 seconds, making for a 28% improvement.</p>
<p>It’s difficult to overstate how impressive this is. With Intel having long since fallen off the curve of Moore’s law, a 30 to 40 percent in a single (customer-facing) hardware generation is incredible.</p>
<p>While developing, I don’t actually spend that much time doing full rebuilds, though. So, how about a slightly more practical example of why this is such a big deal? One thing I’ve previously talked about (as have many others) as making perhaps the single biggest difference in the enjoyablity of programming is the speed of the feedback loop. That is to say, how fast is it from the time when I make a change to my code and hit the Build &amp; Run button to the time at which I can actually see the effect of that change. The smaller the window of time in between those two, the less time there is for me to get distracted, the faster I can figure out whether the change has done what I desired, and the faster I can move on to making the next change. To test this, again using Tusker, I built and launched the app, made a 1-line trivial change to the project (adding <code>view.backgroundColor = .red</code> to a certain view controller), and then rebuilt and launched the app once more. This was more difficult to time accurately, but the M1 was about twice as fast as the Intel machine (~5 seconds down from ~10). Because of this, as I’ve continued to use this computer, iOS development is the task for which it’s made the largest difference in my day-to-day activities.</p>
<p>I ran a few more programming-related tests aside from iOS development, because that’s not all I do. When I first got the machine, I was still in the midst of Advent of Code. The puzzle I had most recently completed was day 11, which was implenting a variation of Conway’s Game of Life. I wrote my solution in Elixir on my laptop, and it worked perfeclty well but was bottlenecked by the fact that looking up the n-th element of a list in Elixir is an O(n) operation. I thought these would make for a decent improvised Elixir benchmark. On my Intel MBP, my solution for part 1 of the puzzle ran in 5.9 seconds and part 2 in 8.0 seconds. With BEAM (the VM used by Erlang/Elixir) running natively on the M1, those times came down to 2.6 seconds and 3.5 seconds respectively, or over <em>twice</em> as fast in both cases.</p>
<p>Out of curiosity (I didn’t expect to actually be able to use this machine for work, mainly because of the memory limitation), I also tried compiling the front-end code for the project I work on at my job. It’s composed of approximately 190 thousand lines in total of TypeScript, Vue, and Sass. Here the performance improvement was less impressive, though not insignificant. Compiling everything from scratch takes 85 seconds on my laptop and 69 seconds on the Mac mini. Additionally, when compiling from scratch, the fans in my laptop spin up and become quite loud (not to mention the chasis gets unpleasantly hot) about 60 seconds in, whereas the Mac mini remains silent for the entire duration. While running Webpack in watch mode and making a trivial change (inserting a <code>console.log</code> call in a Vue component) the time it takes for Webpack to finish recompiling is 19 seconds on my laptop and just 11 on the M1. I’m not certain, but I suspect at least part of the reason the performance improvement with Webpack is so much less than with other tasks, particularly during a clean build, is because Node.js is entirely single-threaded.</p>
<p>As for the hardware itself? It’s fantastic.</p>
<p>Like I went over, performance is great; it easily beats my 16“ Intel MacBook Pro in every task I used it for. The other remarkable thing is the noise. It has a single fan, but if you told me it was completely fanless, I would believe you. Not once have I heard the fan turn on. The environment I’m using it in isn’t incredibly quiet, but it’s really not that loud. The only way I can tell the fan in this computer is spinning is if I put my hand behind the rear exhaust vent or if I press my ear up against its surface.</p>
<p>Unlike on the laptop variants of the M1 machine, where the port selection is a measly two USB-C ports, the mini also has an Ethernet port, two type-A USB ports, a HDMI output, and a separate AC input. On a laptop, the I/O would be very limiting; trying to connect to an external desk setup would require some adapters or a big, fancy (not to mention expensive) Thunderbolt dock. But the mini has enough ports that I can directly connect everything from my setup that I’d normally connect to my laptop (granted, I do use my monitor’s built-in USB hub for both machines).</p>
<p>I’ve had only a few issues I’ve experienced that <em>may</em> be attributable to hardware. The first is that when I’m playing back audio to my Bluetooth headphones, they periodically cut out for a fraction of a second before resuming audio playback. I regularly use these same headphones with my Intel Mac on which I haven’t experienced this issue while running either Catalina or Big Sur. The other issue is that when the Mac mini is connected to my 1440p/144Hz display, it loses the 144Hz setting almost every time I wake it from sleep. Odly, just changing the refresh rate while the resolution is set to “Default for display” does nothing. I have to first change the resolution to be scaled down, and then back to the default before changing the refresh rate actually takes effect. The final issue, which has only happened once in the past month, is that when I woke the computer up from sleep, it had stopped outputting video over the HDMI port. It was still sending video over a USB-C to DisplayPort adapter, but not HDMI. Unplugging and reconnecting the HDMI cable didn’t fix the issue, nor did power cycling the monitor. I had to fully restart the Mac mini to resolve the issue.</p>
<p>But, all in all, for a product that’s the first generation of both a new (to macOS) CPU and GPU architecture, this has been an phenomenally good experience. I am <em>incredibly</em> eager to see both what a higher-performance variant of the M1 looks like and future generations of this architecture.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Calculating the Duration of MP3 Files</title>
      <link>https://shadowfacts.net/2021/mp3-duration/</link>
      <category>elixir</category>
      <guid>https://shadowfacts.net/2021/mp3-duration/</guid>
      <pubDate>Mon, 04 Jan 2021 01:02:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Armed with the ID3 decoder from my <a href="/2020/parsing-id3-tags/" data-link="/2020/parsing-id3-tags/">last post</a>, we can extract most of the metadata from MP3 files. However, the one piece I still want for my music cataloging software is the track duration, which, for the vast majority of my files, is not included in the ID3 tag. Getting the duration of an audio file isn’t as straightforward as I had hoped. One of the easiest solutions would be to just shell out to another piece of software, such as ffmpeg, which can handle a great many audio formats. But that would be boring, and I wanted to minimize the number of dependencies, which meant writing a rudimentary MP3 decoder myself. Luckily, I don’t need to actually playback audio myself, so I can avoid a great deal of complexity. I only need to parse enough of the MP3 file to figure out how long it is.</p>
<!-- excerpt-end -->
<p>First, a overview of the anatomy of an MP3 file. As we went through in the last post, an MP3 file may optionally start with an ID3 tag containing metadata about the track. After that comes the meat of the file: a sequence of MP3 frames. Each frame contains a specific amount of encoded audio data (the actual amount is governed by a number of properties encoded in the frame header). The total duration of the file is simply the sum of the durations of the individual frames. Each MP3 frame starts with a marker sequence of 11 1-bits followed by three bytes of flags describing the contents of the frame and then the audio data itself.</p>
<p>Based on this information, many posts on various internet forums suggest inspecting the first frame to find its bitrate, and then dividing the bit size of the entire file by the bitrate to get the total duration. There are a few problems with this though. The biggest is that whole MP3 files can generally be divided into two groups: constant bitrate (CBR) files and variable bitrate (VBR) ones. Bitrate refers to the number of bits used to represent audio data for a certain time interval. As the name suggests, files encoded with a constant bitrate use the same number of bits per second to represent audio data throughout the entire file. For the naive length estimation method, this is okay (though not perfect, because it doesn’t account for the frame headers and any potential unused space in between frames). In variable bitrate MP3s though, each frame can have a different bitrate, which allows the encoder to work more space-efficiently (because portions of the audio that are less complex can be encoded at a lower bitrate). Because of this, the naive estimation doesn’t work at all (unless, by coincidence, the bitrate of the first frame happens to be close to the average bitrate for the entire file). In order to accurately get the duration for a VBR file, we need to go through every single frame in the file and sum their individual durations. So that’s what we’re gonna do.</p>
<p>The overall structure of the MP3 decoder is going to be fairly similar to the ID3 one. We can even take advantage of the existing ID3 decoder to skip over the ID3 tag at the beginning of the file, thereby avoiding any false syncs (the <code>parse_tag</code> function needs to be amended to return the remaining binary data after the tag in order to do this). From there, it’s simply a matter of scanning through the file looking for the magic sequence of 11 1-bits that mark the frame synchronization point and repeating that until we reach the end of the file.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">get_mp3_duration</span>(<span class="hl-var">data</span>) <span class="hl-kw">when</span> <span class="hl-fn">is_binary</span>(<span class="hl-var">data</span>) <span class="hl-kw">do</span>
  {<span class="hl-cmt">_</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-mod">ID3</span><span class="hl-op">.</span><span class="hl-fn">parse_tag</span>(<span class="hl-var">data</span>)
  <span class="hl-fn">parse_frame</span>(<span class="hl-var">rest</span>, <span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-num">0</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>The <code>parse_frame</code> function takes several arguments in addition to the data. These are the accumulated duration, the number of frames parsed so far, and the byte offset in the file. These last two aren’t strictly needed for parsing the file, but come in very useful if you have to debug any issues. The function has several different cases. The first looks for the sync bits at the start of the binary and, if it finds it, parses the frame header to caclulate the duration, adds it to the accumulator, and recurses. The next case skips a byte from the beginning of the binary and then recurses. And the final case handles an empty binary and simply returns the accumulated duration.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(
	  &lt;&lt;
	    <span class="hl-num">0xFF</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>),
		<span class="hl-num">0b111</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">3</span>),
		<span class="hl-var">version_bits</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-var">layer_bits</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-cmt">_protected</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-var">bitrate_index</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
		<span class="hl-var">sampling_rate_index</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-var">padding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-cmt">_private</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-cmt">_channel_mode_index</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-cmt">_mode_extension</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-cmt">_copyright</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-cmt">_original</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-cmt">_emphasis</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
		<span class="hl-cmt">_rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>
      &gt;&gt;,
      <span class="hl-var">acc</span>,
      <span class="hl-var">frame_count</span>,
      <span class="hl-var">offset</span>
    ) <span class="hl-kw">do</span>
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(&lt;&lt;<span class="hl-cmt">_</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;, <span class="hl-var">acc</span>, <span class="hl-var">frame_count</span>, <span class="hl-var">offset</span>) <span class="hl-kw">do</span>
  <span class="hl-fn">parse_frame</span>(<span class="hl-var">rest</span>, <span class="hl-var">acc</span>, <span class="hl-var">frame_count</span>, <span class="hl-var">offset</span> <span class="hl-op">+</span> <span class="hl-num">1</span>)
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(&lt;&lt;&gt;&gt;, <span class="hl-var">acc</span>, <span class="hl-cmt">_frame_count</span>, <span class="hl-cmt">_offset</span>) <span class="hl-kw">do</span>
  <span class="hl-var">acc</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The main implementation of the <code>parse_frames</code> function isn’t too complicated. It’s just getting a bunch of numbers out of lookup-tables and doing a bit of math. </p>
<p>The first thing we need to know is what kind of frame we are looking at. MP3 frames are divided two ways, by the version of the frame and the layer of the frame. In the header, there are two fields, each two bits wide, that indicate the version and layer. But not every combination of those bits are valid. There are only three versions (and version 2.5 is technically an unnoficial addition at that) and three layers. We can use a couple functions to look up atoms representing the different versions/layers, since it’s more convenient than having to use the raw numeric values in other places. We also return the <code>:invalid</code> atom for version <code>0b01</code> and layer <code>0b00</code> respectively, so that if we enocunter one when parsing a frame, we can immediately stop and look for the next sync point.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defp</span> <span class="hl-fn">lookup_version</span>(<span class="hl-num">0b00</span>), <span class="hl-str">do: </span><span class="hl-str">:version25</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_version</span>(<span class="hl-num">0b01</span>), <span class="hl-str">do: </span><span class="hl-str">:invalid</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_version</span>(<span class="hl-num">0b10</span>), <span class="hl-str">do: </span><span class="hl-str">:version2</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_version</span>(<span class="hl-num">0b11</span>), <span class="hl-str">do: </span><span class="hl-str">:version1</span>

<span class="hl-kw">defp</span> <span class="hl-fn">lookup_layer</span>(<span class="hl-num">0b00</span>), <span class="hl-str">do: </span><span class="hl-str">:invalid</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_layer</span>(<span class="hl-num">0b01</span>), <span class="hl-str">do: </span><span class="hl-str">:layer3</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_layer</span>(<span class="hl-num">0b10</span>), <span class="hl-str">do: </span><span class="hl-str">:layer2</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_layer</span>(<span class="hl-num">0b11</span>), <span class="hl-str">do: </span><span class="hl-str">:layer1</span>
</code></pre>
<p>The next piece of information we need is the sampling rate which is the frequency (in Hertz) with respect to time at which individual audio samples are taken. In the header, it’s also represented by two bits. As before, we pattern match in the function definition to find the actual sampling rate from the index, and return <code>:invalid</code> if the index is not permitted.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-cmt">_version</span>, <span class="hl-num">0b11</span>), <span class="hl-str">do: </span><span class="hl-str">:invalid</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version1</span>, <span class="hl-num">0b00</span>), <span class="hl-str">do: </span><span class="hl-num">44100</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version1</span>, <span class="hl-num">0b01</span>), <span class="hl-str">do: </span><span class="hl-num">48000</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version1</span>, <span class="hl-num">0b10</span>), <span class="hl-str">do: </span><span class="hl-num">32000</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version2</span>, <span class="hl-num">0b00</span>), <span class="hl-str">do: </span><span class="hl-num">22050</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version2</span>, <span class="hl-num">0b01</span>), <span class="hl-str">do: </span><span class="hl-num">24000</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version2</span>, <span class="hl-num">0b10</span>), <span class="hl-str">do: </span><span class="hl-num">16000</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version25</span>, <span class="hl-num">0b00</span>), <span class="hl-str">do: </span><span class="hl-num">11025</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version25</span>, <span class="hl-num">0b01</span>), <span class="hl-str">do: </span><span class="hl-num">12000</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-str">:version25</span>, <span class="hl-num">0b10</span>), <span class="hl-str">do: </span><span class="hl-num">8000</span>
</code></pre>
<p>The last piece of information we need from the header is the bitrate, or the number of bits that are used to represent a single second (or, in our case, the number of kilobits, for simplicity’s sake). The header has a four bit field that represent which index in the lookup table we should use to find the bitrate. But that’s not all the information that’s necessary. In order to know which lookup table to use, we also need the version and layer of the frame. For each version and layer combination there is a different set of bitrates that the frame may use.</p>
<p>So, the <code>lookup_bitrate</code> function will need to take three parameters: the version, layer, and bitrate index. First off, indices 0 and 15 are reserved by the spec, so we can just return the <code>:invalid</code> atom regardless of the version or layer. For the other version/layer combinations, we simply look up the index in the appropriate list. A couple things to note are that in version 2, layers 2 and 3 use the same bitrates, and all layers for version 2.5 use the same bitrates as version 2.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-attr">@</span><span class="hl-attr">v1_l1_bitrates</span> [<span class="hl-str">:invalid</span>, <span class="hl-num">32</span>, <span class="hl-num">64</span>, <span class="hl-num">96</span>, <span class="hl-num">128</span>, <span class="hl-num">160</span>, <span class="hl-num">192</span>, <span class="hl-num">224</span>, <span class="hl-num">256</span>, <span class="hl-num">288</span>, <span class="hl-num">320</span>, <span class="hl-num">352</span>, <span class="hl-num">384</span>, <span class="hl-num">416</span>, <span class="hl-num">448</span>, <span class="hl-str">:invalid</span>]
<span class="hl-attr">@</span><span class="hl-attr">v1_l2_bitrates</span> [<span class="hl-str">:invalid</span>, <span class="hl-num">32</span>, <span class="hl-num">48</span>, <span class="hl-num">56</span>, <span class="hl-num">64</span>, <span class="hl-num">80</span>, <span class="hl-num">96</span>, <span class="hl-num">112</span>, <span class="hl-num">128</span>, <span class="hl-num">160</span>, <span class="hl-num">192</span>, <span class="hl-num">224</span>, <span class="hl-num">256</span>, <span class="hl-num">320</span>, <span class="hl-num">384</span>, <span class="hl-str">:invalid</span>]
<span class="hl-attr">@</span><span class="hl-attr">v1_l3_bitrates</span> [<span class="hl-str">:invalid</span>, <span class="hl-num">32</span>, <span class="hl-num">40</span>, <span class="hl-num">48</span>, <span class="hl-num">56</span>, <span class="hl-num">64</span>, <span class="hl-num">80</span>, <span class="hl-num">96</span>, <span class="hl-num">112</span>, <span class="hl-num">128</span>, <span class="hl-num">160</span>, <span class="hl-num">192</span>, <span class="hl-num">224</span>, <span class="hl-num">256</span>, <span class="hl-num">320</span>, <span class="hl-str">:invalid</span>]
<span class="hl-attr">@</span><span class="hl-attr">v2_l1_bitrates</span> [<span class="hl-str">:invalid</span>, <span class="hl-num">32</span>, <span class="hl-num">48</span>, <span class="hl-num">56</span>, <span class="hl-num">64</span>, <span class="hl-num">80</span>, <span class="hl-num">96</span>, <span class="hl-num">112</span>, <span class="hl-num">128</span>, <span class="hl-num">144</span>, <span class="hl-num">160</span>, <span class="hl-num">176</span>, <span class="hl-num">192</span>, <span class="hl-num">224</span>, <span class="hl-num">256</span>, <span class="hl-str">:invalid</span>]
<span class="hl-attr">@</span><span class="hl-attr">v2_l2_l3_bitrates</span> [<span class="hl-str">:invalid</span>, <span class="hl-num">8</span>, <span class="hl-num">16</span>, <span class="hl-num">24</span>, <span class="hl-num">32</span>, <span class="hl-num">40</span>, <span class="hl-num">48</span>, <span class="hl-num">56</span>, <span class="hl-num">64</span>, <span class="hl-num">80</span>, <span class="hl-num">96</span>, <span class="hl-num">112</span>, <span class="hl-num">128</span>, <span class="hl-num">144</span>, <span class="hl-num">160</span>, <span class="hl-str">:invalid</span>]

<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-cmt">_version</span>, <span class="hl-cmt">_layer</span>, <span class="hl-num">0</span>), <span class="hl-str">do: </span><span class="hl-str">:invalid</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-cmt">_version</span>, <span class="hl-cmt">_layer</span>, <span class="hl-num">0xF</span>), <span class="hl-str">do: </span><span class="hl-str">:invalid</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer1</span>, <span class="hl-var">index</span>), <span class="hl-str">do: </span><span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-attr">@</span><span class="hl-attr">v1_l1_bitrates</span>, <span class="hl-var">index</span>)
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer2</span>, <span class="hl-var">index</span>), <span class="hl-str">do: </span><span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-attr">@</span><span class="hl-attr">v1_l2_bitrates</span>, <span class="hl-var">index</span>)
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer3</span>, <span class="hl-var">index</span>), <span class="hl-str">do: </span><span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-attr">@</span><span class="hl-attr">v1_l3_bitrates</span>, <span class="hl-var">index</span>)
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-var">v</span>, <span class="hl-str">:layer1</span>, <span class="hl-var">index</span>) <span class="hl-kw">when</span> <span class="hl-var">v</span> <span class="hl-kw">in</span> [<span class="hl-str">:version2</span>, <span class="hl-str">:version25</span>], <span class="hl-str">do: </span><span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-attr">@</span><span class="hl-attr">v2_l1_bitrates</span>, <span class="hl-var">index</span>)
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-var">v</span>, <span class="hl-var">l</span>, <span class="hl-var">index</span>) <span class="hl-kw">when</span> <span class="hl-var">v</span> <span class="hl-kw">in</span> [<span class="hl-str">:version2</span>, <span class="hl-str">:version25</span>] <span class="hl-kw">and</span> <span class="hl-var">l</span> <span class="hl-kw">in</span> [<span class="hl-str">:layer2</span>, <span class="hl-str">:layer3</span>], <span class="hl-str">do: </span><span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">at</span>(<span class="hl-attr">@</span><span class="hl-attr">v2_l2_l3_bitrates</span>, <span class="hl-var">index</span>)
</code></pre>
<p>One could do some fancy metaprogramming to generate a function case for each version/layer/index combination to avoid the <code>Enum.at</code> call at runtime and avoid some of the code repetition, but this is perfectly fine.</p>
<p>With those four functions implemented, we can return to the body of the main <code>parse_frame</code> implementation.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-kw">with</span> <span class="hl-var">version</span> <span class="hl-kw">when</span> <span class="hl-var">version</span> <span class="hl-op">!=</span> <span class="hl-str">:invalid</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">lookup_version</span>(<span class="hl-var">version_bits</span>),
       <span class="hl-var">layer</span> <span class="hl-kw">when</span> <span class="hl-var">layer</span> <span class="hl-op">!=</span> <span class="hl-str">:invalid</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">lookup_layer</span>(<span class="hl-var">layer_bits</span>),
       <span class="hl-var">sampling_rate</span> <span class="hl-kw">when</span> <span class="hl-var">sampling_rate</span> <span class="hl-op">!=</span> <span class="hl-str">:invalid</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">lookup_sampling_rate</span>(<span class="hl-var">version</span>, <span class="hl-var">sampling_rate_index</span>),
       <span class="hl-var">bitrate</span> <span class="hl-kw">when</span> <span class="hl-var">bitrate</span> <span class="hl-op">!=</span> <span class="hl-str">:invalid</span> <span class="hl-op">&lt;-</span> <span class="hl-fn">lookup_bitrate</span>(<span class="hl-var">version</span>, <span class="hl-var">layer</span>, <span class="hl-var">bitrate_index</span>) <span class="hl-kw">do</span>
  <span class="hl-kw">else</span>
    <span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
      &lt;&lt;<span class="hl-cmt">_</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">data</span>
      <span class="hl-fn">parse_frame</span>(<span class="hl-var">rest</span>, <span class="hl-var">acc</span>, <span class="hl-var">frame_count</span>, <span class="hl-var">offset</span> <span class="hl-op">+</span> <span class="hl-num">1</span>)
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>We call the individual lookup functions for each of the pieces of data we need from the header. Using Elixir’s <code>with</code> statement lets us pattern match on a bunch of values together. If any of the functions return <code>:invalid</code>, the pattern match will fail and it will fall through to the <code>else</code> part of the with statement that matches anything. If that happens, we skip the first byte from the binary and recurse, looking for the next potential sync point. </p>
<p>Inside the main body of the with statement, we need to find the number of samples in the frame.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-kw">with</span> <span class="hl-var">...</span> <span class="hl-kw">do</span>
    <span class="hl-var">samples</span> <span class="hl-op">=</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-var">version</span>, <span class="hl-var">layer</span>)
  <span class="hl-kw">else</span>
    <span class="hl-var">...</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The number of samples per frame is once again given by a lookup table, this time based only on the version and layer of the frame. As before, the version 2.5 constants are the same as the ones for version 2.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer1</span>), <span class="hl-str">do: </span><span class="hl-num">384</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer2</span>), <span class="hl-str">do: </span><span class="hl-num">1152</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-str">:version1</span>, <span class="hl-str">:layer3</span>), <span class="hl-str">do: </span><span class="hl-num">1152</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-var">v</span>, <span class="hl-str">:layer1</span>) <span class="hl-kw">when</span> <span class="hl-var">v</span> <span class="hl-kw">in</span> [<span class="hl-str">:version2</span>, <span class="hl-str">:version25</span>], <span class="hl-str">do: </span><span class="hl-num">384</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-var">v</span>, <span class="hl-str">:layer2</span>) <span class="hl-kw">when</span> <span class="hl-var">v</span> <span class="hl-kw">in</span> [<span class="hl-str">:version2</span>, <span class="hl-str">:version25</span>], <span class="hl-str">do: </span><span class="hl-num">1152</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_samples_per_frame</span>(<span class="hl-var">v</span>, <span class="hl-str">:layer3</span>) <span class="hl-kw">when</span> <span class="hl-var">v</span> <span class="hl-kw">in</span> [<span class="hl-str">:version2</span>, <span class="hl-str">:version25</span>], <span class="hl-str">do: </span><span class="hl-num">576</span>
</code></pre>
<p>Now, we have enough information to start calculating the actual byte size of the frame. This involves a bit of math that could be done all at once, but let’s break it down for clarity.</p>
<p>First, we need to know the duration of a single sample. We have the sampling rate, which is the number of samples per second, so dividing 1 by that value gives us the duration of an individual sample.</p>
<p>Then, since we know the number of samples in the entire frame, we can multiply that by the duration of an individual sample to get the total duration for the entire frame (this is the same value we’ll later add to the duration accumulator).</p>
<p>Next, we have the bitrate from the lookup function, but it’s in kilobits per second. When determining the frame size, we want the unit to be bytes, so we first multiply by 1000 to get bits per second, and then divide by 8 to get bytes per second.</p>
<p>The bytes per second value can then be multiplied by the frame duration to get the number of bytes. Finally, the frame may have padding to ensure that the bitrate exactly matches its size and duration. The size of the padding depends on the layer: for layer 1 the padding is 4 bytes and for layers 2 and 3, 1 byte.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">defp</span> <span class="hl-fn">get_frame_size</span>(<span class="hl-var">samples</span>, <span class="hl-var">layer</span>, <span class="hl-var">kbps</span>, <span class="hl-var">sampling_rate</span>, <span class="hl-var">padding</span>) <span class="hl-kw">do</span>
  <span class="hl-var">sample_duration</span> <span class="hl-op">=</span> <span class="hl-num">1</span> <span class="hl-op">/</span> <span class="hl-var">sampling_rate</span>
  <span class="hl-var">frame_duration</span> <span class="hl-op">=</span> <span class="hl-var">samples</span> <span class="hl-op">*</span> <span class="hl-var">sample_duration</span>
  <span class="hl-var">bytes_per_second</span> <span class="hl-op">=</span> <span class="hl-var">kbps</span> <span class="hl-op">*</span> <span class="hl-num">1000</span> <span class="hl-op">/</span> <span class="hl-num">8</span>
  <span class="hl-var">size</span> <span class="hl-op">=</span> <span class="hl-fn">floor</span>(<span class="hl-var">frame_duration</span> <span class="hl-op">*</span> <span class="hl-var">bytes_per_second</span>)

  <span class="hl-kw">if</span> <span class="hl-var">padding</span> <span class="hl-op">==</span> <span class="hl-num">1</span> <span class="hl-kw">do</span>
    <span class="hl-var">size</span> <span class="hl-op">+</span> <span class="hl-fn">lookup_slot_size</span>(<span class="hl-var">layer</span>)
  <span class="hl-kw">else</span>
    <span class="hl-var">size</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>

<span class="hl-kw">defp</span> <span class="hl-fn">lookup_slot_size</span>(<span class="hl-str">:layer1</span>), <span class="hl-str">do: </span><span class="hl-num">4</span>
<span class="hl-kw">defp</span> <span class="hl-fn">lookup_slot_size</span>(<span class="hl-var">l</span>) <span class="hl-kw">when</span> <span class="hl-var">l</span> <span class="hl-kw">in</span> [<span class="hl-str">:layer2</span>, <span class="hl-str">:layer3</span>], <span class="hl-str">do: </span><span class="hl-num">1</span>
</code></pre>
<p>One thing to note is that we <code>floor</code> the size before returning it. All of the division changes the value into a floating point, albeit one for which we know the decimal component will be zero. <code>floor</code>ing it turns it into an actual integer (<code>1</code> rather than <code>1.0</code>) because using a floating point value in a binary pattern will cause an error.</p>
<p>With that implemented, we can call it in the <code>parse_frame</code> implementation to get the number of bytes that we need to skip. We also perform the same calculation to get the frame duration. Then we can skip the requisite number of bytes of data, add the values we calculated to the various accumulators and recurse.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frame</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-kw">with</span> <span class="hl-var">...</span> <span class="hl-kw">do</span>
    <span class="hl-cmt"># ...</span>
	<span class="hl-var">frame_size</span> <span class="hl-op">=</span> <span class="hl-fn">get_frame_size</span>(<span class="hl-var">samples</span>, <span class="hl-var">layer</span>, <span class="hl-var">bitrate</span>, <span class="hl-var">sampling_rate</span>, <span class="hl-var">padding</span>)
	<span class="hl-var">frame_duration</span> <span class="hl-op">=</span> <span class="hl-var">samples</span> <span class="hl-op">/</span> <span class="hl-var">sampling_rate</span>
	&lt;&lt;<span class="hl-cmt">_skipped</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-var">frame_size</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">data</span>
	<span class="hl-fn">parse_frame</span>(<span class="hl-var">rest</span>, <span class="hl-var">acc</span> <span class="hl-op">+</span> <span class="hl-var">frame_duration</span>, <span class="hl-var">frame_count</span> <span class="hl-op">+</span> <span class="hl-num">1</span>, <span class="hl-var">offset</span> <span class="hl-op">+</span> <span class="hl-var">frame_size</span>)
  <span class="hl-kw">else</span>
    <span class="hl-var">...</span>
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>And with that, we can accurately find the duration of any MP3 file!</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-var">iex</span><span class="hl-op">&gt;</span> <span class="hl-var">data</span> <span class="hl-op">=</span> <span class="hl-mod">File</span><span class="hl-op">.</span><span class="hl-fn">read!</span>(<span class="hl-str">&quot;test.mp3&quot;</span>)
<span class="hl-var">iex</span><span class="hl-op">&gt;</span> <span class="hl-mod">MP3</span><span class="hl-op">.</span><span class="hl-fn">get_mp3_duration</span>(<span class="hl-var">data</span>)
<span class="hl-num">452.20571428575676</span> <span class="hl-cmt"># 7 minutes and 32 seconds</span>
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Parsing ID3 Metadata in Elixir</title>
      <link>https://shadowfacts.net/2020/parsing-id3-tags/</link>
      <category>elixir</category>
      <guid>https://shadowfacts.net/2020/parsing-id3-tags/</guid>
      <pubDate>Tue, 08 Dec 2020 01:26:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>On and off for the past year and a half or so, I’ve been working on a small side project to catalog and organize my music library, which is predominantly composed of MP3 files<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. There are existing pieces of software out there that will do this (such as Beets and Airsonic), but, as many a programmer will attest to, sometimes it’s just more fun to build your own. The choice of language was easy. For a couple years now, Elixir has been my favorite for any back-end web dev. I also had an inkling that its powerful pattern matching facilities could work on arbitrary binary data—perfect for parsing file formats.</p>
<p>I knew that MP3 files had some embedded metadata, only for the reason that looking at most tracks in Finder shows album artwork and information about the track. Cursory googling led me to the <a href="https://id3.org/" data-link="id3.org">ID3 spec</a>.</p>
<!-- excerpt-end -->
<p>Initially, I found a package on Hex for parsing ID3 tags from Elixir. It wasn’t implemented directly in Elixir though, instead it used a NIF: a Natively Implemented Function. NIFs are pieces of C code which are used to implement functions that are accessible to Erlang/Elixir, which is useful if very high performance is needed. But the NIF wouldn’t compile on my machine, so rather than trying to fix it, I decided to try and parse the ID3 data myself. Fortunately, Elixir is a very nice language for parsing binary file formats.</p>
<h2 id="binary-pattern-matching"><a href="#binary-pattern-matching" class="header-anchor" aria-hidden="true" role="presentation">##</a> Binary Pattern Matching</h2>
<p>What makes Elixir so nice for parsing file formats? Pattern matching. Specifically bitstring pattern matching, but let’s start with ordinary pattern matching. (If you already know this, <a href="#parsing-tags" data-link="#parsing-tags">skip ahead</a>.)</p>
<p>Pattern matching in code is, generally speaking, describing what you expect the shape of a piece of data to be and pulling specific parts of it out. Let’s say you have a tuple of two elements, and you want to make sure that the first element is a string containing a certain value and you want to bind the second value to a variable. You could do this in two statements, or you could pattern match on it.</p>
<pre class="highlight" data-lang="elixir"><code>{<span class="hl-str">&quot;foo&quot;</span>, <span class="hl-var">my_variable</span>} <span class="hl-op">=</span> {<span class="hl-str">&quot;foo&quot;</span>, <span class="hl-num">1234</span>}
<span class="hl-mod">IO</span><span class="hl-op">.</span><span class="hl-fn">inspect</span>(<span class="hl-var">my_variable</span>) <span class="hl-cmt"># =&gt; 1234</span>
</code></pre>
<p>This becomes even more powerful once you learn that you can use pattern matching in function parameters, so you can provide two completely different implementations based on some aspect of a parameter:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">is_foo</span>(<span class="hl-str">&quot;foo&quot;</span>) <span class="hl-kw">do</span>
  <span class="hl-const">true</span>
<span class="hl-kw">end</span>
<span class="hl-kw">def</span> <span class="hl-fn">is_foo</span>(<span class="hl-var">value</span>) <span class="hl-kw">do</span>
  <span class="hl-const">false</span>
<span class="hl-kw">end</span>
<span class="hl-fn">is_foo</span>(<span class="hl-str">&quot;foo&quot;</span>) <span class="hl-cmt"># =&gt; true</span>
<span class="hl-fn">is_foo</span>(<span class="hl-num">42</span>) <span class="hl-cmt"># =&gt; false</span>
</code></pre>
<p>Next: pattern matching bitstrings. A bitstring in Elixir/Erlang is just a big ol’ sequence of bits. Additionally, a binary is a special case of a bitstring, where the number of bits is evenly divisible by 8, making it a sequence of bytes. Bitstrings and binaries are written in between double angle brackets, with individual values separated by commas. Unless you specify otherwise, each value is the size of a single byte.</p>
<pre class="highlight" data-lang="elixir"><code>&lt;&lt;<span class="hl-var">first</span>, <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> &lt;&lt;<span class="hl-num">1</span>, <span class="hl-num">2</span>, <span class="hl-num">3</span>&gt;&gt;
<span class="hl-var">first</span> <span class="hl-cmt"># =&gt; 1</span>
<span class="hl-var">rest</span> <span class="hl-cmt"># =&gt; &lt;&lt;2, 3&gt;&gt;</span>
</code></pre>
<p>Here, we’re binding the first byte of the binary to the variable <code>first</code>, and any remaining bytes to <code>rest</code>. When binding variables (or specifying fixed values, as we’ll see shortly) in bitstring pattern matching, two colons and the type of the value follow the name. The size determines how much of the bitstring each value will match. This information is critical, since without it, there may be many ways for a certain pattern to match a bitstring leaving the programmer’s intention ambiguous. By default, each element in a bitstring match has a size of a single byte. Anything else must be explicitly specified. Here <code>rest</code>, is specified to be a binary, meaning it will be a sequence of bytes.</p>
<p>One key thing to note is that the last element of a match is special. Unlike the preceding elemens, its type can be given as just <code>bitstring</code> or <code>binary</code>, without specifying the size. This lets you get all the remaining data out of the bitstring without knowing how long it is, similar to getting the tail of a list.</p>
<p>You can also match specific sizes, including across multiple bytes and specific numbers of bits:</p>
<pre class="highlight" data-lang="elixir"><code>&lt;&lt;<span class="hl-var">first</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">bitstring</span>&gt;&gt; <span class="hl-op">=</span> &lt;&lt;<span class="hl-num">0xFF</span>&gt;&gt;
<span class="hl-var">first</span> <span class="hl-cmt"># =&gt; 15</span>
<span class="hl-var">rest</span> <span class="hl-cmt"># =&gt; &lt;&lt;15::size(4)&gt;&gt;</span>
</code></pre>
<p>This in particular is very useful for parsing binary formats, since it lets you easily unpack bytes which contain multiple bit flags without having to do any bitwise math yourself.</p>
<h2 id="parsing-tags"><a href="#parsing-tags" class="header-anchor" aria-hidden="true" role="presentation">##</a> Parsing Tags</h2>
<p>Ok, now with that under our belts can we finally parse some ID3 tags? Actually, not quite yet. First off, I’m only looking at ID3v2 tags in this, since none of the music I’ve been testing this against uses v1. Second, there are two slightly different versions of the v2 spec that matter: Version <a href="https://id3.org/id3v2.4.0-structure" data-link="id3.org/id3v2.4.0-structure">2.4.0</a> and version <a href="https://id3.org/id3v2.3.0" data-link="id3.org/id3v2.3.0">2.3.0</a>. At first glance, they’re similar, but there are a number of differences lurking beneath the surface that tripped me up as I was building this. Alright, now let’s really get started.</p>
<p>Through the magic of pattern matching, we can define a function that takes a single binary as an argument. There will be two implementations: one that’s used if the data begins with an ID3 tag and one for if it doesn’t. The fallback implementation accepts anything as its parameter and returns an empty map, indicating that there was no ID3 data. The other implementation will match against the contents of the binary, expecting it to match the defined format of an ID3v2 tag.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_tag</span>(&lt;&lt;
	  <span class="hl-str">&quot;ID3&quot;</span>,
	  <span class="hl-var">major_version</span><span class="hl-op">::</span><span class="hl-var">integer</span>,
	  <span class="hl-cmt">_revision</span><span class="hl-op">::</span><span class="hl-var">integer</span>,
	  <span class="hl-cmt">_unsynchronized</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
	  <span class="hl-var">extended_header</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
	  <span class="hl-cmt">_experimental</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
	  <span class="hl-cmt">_footer</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
	  <span class="hl-num">0</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
	  <span class="hl-var">tag_size_synchsafe</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
	  <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>
    &gt;&gt;) <span class="hl-kw">do</span>
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">parse_tag</span>(<span class="hl-cmt">_</span>) <span class="hl-kw">do</span>
  %{}
<span class="hl-kw">end</span>
</code></pre>
<p>ID3v2 specifies that a file with an ID3v2 tag should begin with the ASCII byte sequence representing “ID3”. We can match this directly in the pattern, because strings in Elixir/Erlang are just binaries. The magic string is followed by an integer representing the major version (4 for ID3 version 2.4 and 3 for 2.3) and another integer representing the least significant component of the ID3 spec version used by the file (we don’t care about it because no minor revisions of the spec have been released). It’s followed by a series of four 1-bit flags and then four unused bitflags, which should always be zero. After that is a 32-bit number that contains the total size of the ID3 tag. The variable name for this is <code>tag_size_synchsafe</code> because it’s encoded with ID3v2’s special synchsafe scheme, which we’ll get to shortly. The remainder of the file is bound as a binary to the <code>rest</code> variable.</p>
<p>To understand why ID3 encodes many numbers as “synchsafe”, it’s important to remember that ID3 was designed to carry metadata specifically for MP3 files. As such, it had to take special care to not interfere with existing MP3-handling software that may not have understood ID3 tags. The MPEG audio format encodes an audio stream into individual frames of audio data, each of which starts with a byte composed of all 1s (i.e., 0xFF). This is done to let audio players easily seek through a bytestream and locate a point where valid audio data starts (called synchronizing), without having to parse the entire file leading up to that point. Because the 0xFF byte is the sync marker, its presence in an ID3 tag (which is, of course, not valid audio data), would cause players to start trying to play back nonsense data. So, within an ID3 tag, all numbers need to be encoded in such a way that an all-1s byte never occurs. This is where the synchsafe scheme comes (so called because it’s safe from causing false-syncs).</p>
<p>Synchsafe integers work by only using the seven lower bits of each byte and leaving the highest-order bit as 0, thereby preventing any 0xFF bytes, and false syncs, from occurring. The number 255 would be encoded as 0b101111111 (decimal 383). This means that, for every byte of space used, a synchsafe integer stores only seven bits of information. Decoding these integers is pretty easy. For the simplest case of a single byte, nothing needs to be done: the value of the byte is the value of the entire number. Decoding multi-byte synchsafe integers is slightly more complicated, but not terribly so. We need to go through each byte, building up an integer. We shift each byte to the left by seven times the index of the byte in the multi-byte synchsafe integer.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">use</span> <span class="hl-mod">Bitwise</span>

<span class="hl-kw">def</span> <span class="hl-fn">decode_synchsafe_integer</span>(&lt;&lt;<span class="hl-var">b</span>&gt;&gt;) <span class="hl-kw">do</span>
  <span class="hl-var">b</span>
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">decode_synchsafe_integer</span>(<span class="hl-var">binary</span>) <span class="hl-kw">do</span>
  <span class="hl-var">binary</span>
  <span class="hl-op">|&gt;</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">bin_to_list</span>()
  <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">reverse</span>()
  <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">with_index</span>()
  <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">reduce</span>(<span class="hl-num">0</span>, <span class="hl-kw">fn</span> {<span class="hl-var">el</span>, <span class="hl-var">index</span>}, <span class="hl-var">acc</span> <span class="hl-op">-&gt;</span>
  	<span class="hl-var">acc</span> <span class="hl-op">|||</span> (<span class="hl-var">el</span> <span class="hl-op">&lt;&lt;&lt;</span> (<span class="hl-var">index</span> <span class="hl-op">*</span> <span class="hl-num">7</span>))
  <span class="hl-kw">end</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>The <code>bin_to_list</code> function takes a binary, which is just a sequence of bytes, and converts it into an actual list that we can use with the functions in the <code>Enum</code> module. This list is reversed and its elements are converted into tuples which include the index. The list needs to be reversed first because the bytes in synchsafe integers are big-endian, meaning the most significant comes first. We want the indices to match this, with the highest index being with the highest-order byte. So, we reverse the list so both the bytes and indices are going from least significant and smallest to most significant and greatest. From there, each byte is shifted left by seven times the index, eliminating the gaps between the actual information-carrying bits and OR’d together.</p>
<p>Here’s what decoding the synchsafe number represented as 0b10101001000011010 (0x1521A) would look like:</p>
<pre class="highlight" data-lang=""><code>          00000001 01010010 00011010
reversed: 00011010 01010010 00000001
          Index 0  Index 1  Index 2

00011010 &lt;&lt;&lt;  0 =          00011010
01010010 &lt;&lt;&lt;  7 = 00101001 00000000
00000001 &lt;&lt;&lt; 14 = 01000000 00000000

	00000000 00011010
    00101001 00000000
||| 01000000 00000000
---------------------
    01101001 00011010 = 26906
</code></pre>
<p>You may have noticed the <code>unsynchronized</code> flag in the tag header. The ID3 unschronization scheme is another way of preventing false syncs in longer blocks of data within the tag (such as image data in the frame used for album artwork). I elected not to handle this flag for now, since none of the tracks in my library have the flag set. The ID3v2.4 spec says the unsynchronization scheme is primarily intended to prevent old software which isn’t aware of ID3 tags from incorrectly trying to sync onto data in the ID3 tag. Since the ID3v2 spec is over 20 years old, pieces of software which aren’t aware of it are few and far between, so I guess the unsynchronization scheme has fallen out of favor.</p>
<p>So, since we’ve gotten the 4-byte binary that contains the tag size out of the header, we can use the <code>decode_synchsafe_integer</code> function to decode it. </p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_tag</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-var">tag_size</span> <span class="hl-op">=</span> <span class="hl-fn">decode_synchsafe_integer</span>(<span class="hl-var">tag_size_synchsafe</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>We’ll use the tag size when we start decoding ID3 frames, to ensure that we don’t go over the end of the ID3 tag into the actual MP3 data. But before we can start parsing frames, we need to take care of the extended header, if it’s present. The extended header contains optional extra data that doesn’t fit into the regular header and isn’t part of any frame. This is where the differences between v2.3 and v2.4 come into play. In version 2.3 of the spec, the size of the extended header is fixed at either 6 or 10 bytes, depending on whether a checksum is present. In version 2.4, the size is variable and depends on various flags in the extended header.</p>
<p>So, when parsing the extended header, we’ll use a function that takes the major version specified in the tag header, and parses it differently depending on the version. Since, for my purposes, I don’t care about any of the data that it would provide, I just discard it and return the binary containing all the bytes after the extended header. The <code>skip_extended_header</code> function also returns the length of the extended header, so that we can take it into account when calculating how long the remainder of the tag is (since the tag size in the ID3 header includes the extended header’s length).</p>
<p>For the implementation for version 2.3, the extended header begins with the size of the extended header (including the fixed length of six bytes). This is not encoded as synchsafe (though it is in version 2.4), so we can subtract the six bytes the pattern match has already skipped, and skip any remaining bytes. The length of the extended header is defined by the spec to always be 6 or 10 bytes, so we can safely subtract 6 without breaking anything (matching a binary of size 0 in the pattern match works perfectly fine).</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">skip_extended_header</span>(<span class="hl-num">3</span>, &lt;&lt;
	  <span class="hl-var">ext_header_size</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">32</span>),
	  <span class="hl-cmt">_flags</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">16</span>),
	  <span class="hl-cmt">_padding_size</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">32</span>),
	  <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>
    &gt;&gt;) <span class="hl-kw">do</span>
  <span class="hl-var">remaining_ext_header_size</span> <span class="hl-op">=</span> <span class="hl-var">ext_header_size</span> <span class="hl-op">-</span> <span class="hl-num">6</span>
  &lt;&lt;<span class="hl-cmt">_</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-var">remaining_ext_header_size</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">rest</span>
  {<span class="hl-var">rest</span>, <span class="hl-var">ext_header_size</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>For the version 2.4 implementation, it’s a little bit more complicated. The extended header still starts with four bytes giving the size of the extended header (though this time encoded as a synchsafe number), then has a 0x01 byte, and then a byte for the flags. After those six bytes, there may be more data depending on what flags were set (an additional six bytes for CRC and 2 bytes for tag restrictions). This time, we need to decode the size from synchsafe, and then we can subtract the fixed length and skip over the remainder as before.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">skip_extended_header</span>(<span class="hl-num">4</span>, &lt;&lt;
	  <span class="hl-var">ext_header_size_synchsafe</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">32</span>),
	  <span class="hl-num">1</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>),
	  <span class="hl-cmt">_flags</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>),
	  <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>
    &gt;&gt;) <span class="hl-kw">do</span>
  <span class="hl-var">ext_header_size</span> <span class="hl-op">=</span> <span class="hl-fn">decode_synchsafe_integer</span>(<span class="hl-var">ext_header_size_synchsafe</span>)
  <span class="hl-var">remaining_ext_header_size</span> <span class="hl-op">=</span> <span class="hl-var">ext_header_size</span> <span class="hl-op">-</span> <span class="hl-num">6</span>
  &lt;&lt;<span class="hl-cmt">_</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-var">remaining_ext_header_size</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">rest</span>
  {<span class="hl-var">rest</span>, <span class="hl-var">ext_header_size</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>In the main <code>parse_tag</code> function, we can skip over the extended header if the corresponding flag bit is set. If it isn’t, no data needs to be skipped and the length of the extended heder is zero. With that, we can finally procede to parsing the ID3 frames to get out the actually useful data.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_tag</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-var">tag_size</span> <span class="hl-op">=</span> <span class="hl-fn">decode_synchsafe_integer</span>(<span class="hl-var">tag_size_synchsafe</span>)

  {<span class="hl-var">rest</span>, <span class="hl-var">ext_header_size</span>} <span class="hl-op">=</span>
    <span class="hl-kw">if</span> <span class="hl-var">extended_header</span> <span class="hl-op">==</span> <span class="hl-num">1</span> <span class="hl-kw">do</span>
	  <span class="hl-fn">skip_extended_header</span>(<span class="hl-var">major_version</span>, <span class="hl-var">rest</span>)
	<span class="hl-kw">else</span>
	  {<span class="hl-var">rest</span>, <span class="hl-num">0</span>}
	<span class="hl-kw">end</span>

  <span class="hl-fn">parse_frames</span>(<span class="hl-var">major_version</span>, <span class="hl-var">rest</span>, <span class="hl-var">tag_size</span> <span class="hl-op">-</span> <span class="hl-var">extended_header</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>When passing the remaining tag length to the <code>parse_frames</code> function, we need to subtract the extended header length from the tag size that we got from the ID3 header, because it already includes the extended header that we’ve already skipped over. While parsing frames, we’ll use it as a confirmation that we haven’t overrun the end of the tag.</p>
<p>The <code>parse_frames</code> function also receives the major version of the tag, the actual binary data, and a list of accumulated frames so far (which defaults to an empty list). It will be called in a recursive loop, advancing through the binary and accumulating frames.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(<span class="hl-var">major_version</span>, <span class="hl-var">data</span>, <span class="hl-var">tag_length_remaining</span>, <span class="hl-var">frames</span> <span class="hl-op">\\</span> [])
</code></pre>
<p>The first case of the function is for if it’s reached the total length of the tag, in which case it will just convert the accumulated tags into a map, and return the data that’s left (we want to return whatever data’s left after the end of the ID3 tag so that it can be used by other parts of the code, say, an MP3 parser…). We can just directly convert the list of frames into a map because, as you’ll see shortly, each frame is a tuple of the name of the frame and its data, in whatever form that may be.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(<span class="hl-cmt">_</span>, <span class="hl-var">data</span>, <span class="hl-var">tag_length_remaining</span>, <span class="hl-var">frames</span>) 
    <span class="hl-kw">when</span> <span class="hl-var">tag_length_remaining</span> <span class="hl-op">&lt;=</span> <span class="hl-num">0</span> <span class="hl-kw">do</span>
  {<span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">new</span>(<span class="hl-var">frames</span>), <span class="hl-var">data</span>}
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(<span class="hl-cmt">_</span>, <span class="hl-var">data</span>, <span class="hl-cmt">_</span>, <span class="hl-var">frames</span>) <span class="hl-kw">do</span>
  {<span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">new</span>(<span class="hl-var">frames</span>), <span class="hl-var">data</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>The fallback case of <code>parse_frames</code>, which will be called if the we can’t find a valid ID3 frame header in the data stream, does the same thing.</p>
<p>The bulk of the work is done by the next implementation of <code>parse_frames</code>. It starts by matching a valid frame header from the beginning of the binary. After that, it first needs to get the actual size of the frame. In version 2.4 of ID3, the frame size is encoded with the synchsafe scheme, but in 2.3, it’s just a plain 32-bit integer. After that, it calculates the entire size of the frame by adding 10 bytes (since the value in the header does not include the size of the header) and then subtracting that from the total tag length remaining, to figure out how much of the tag will be left after the current frame is parsed.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(
      <span class="hl-var">major_version</span>,
      &lt;&lt;
        <span class="hl-var">frame_id</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
        <span class="hl-var">frame_size_maybe_synchsafe</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
        <span class="hl-num">0</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_tag_alter_preservation</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_file_alter_preservation</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_read_only</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-num">0</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">4</span>),
        <span class="hl-cmt">_grouping_identity</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-num">0</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">2</span>),
        <span class="hl-cmt">_compression</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_encryption</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_unsynchronized</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-cmt">_has_data_length_indicator</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
		<span class="hl-cmt">_unused</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">1</span>),
        <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>
      &gt;&gt;,
      <span class="hl-var">tag_length_remaining</span>,
      <span class="hl-var">frames</span>
    ) <span class="hl-kw">do</span>

  <span class="hl-var">frame_size</span> <span class="hl-op">=</span>
    <span class="hl-kw">case</span> <span class="hl-var">major_version</span> <span class="hl-kw">do</span>
      <span class="hl-num">4</span> <span class="hl-op">-&gt;</span>
        <span class="hl-fn">decode_synchsafe_integer</span>(<span class="hl-var">frame_size_maybe_synchsafe</span>)
      <span class="hl-num">3</span> <span class="hl-op">-&gt;</span>
        &lt;&lt;<span class="hl-var">size</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">32</span>)&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">frame_size_maybe_synchsafe</span>
		<span class="hl-var">size</span>
	<span class="hl-kw">end</span>

  <span class="hl-var">total_frame_size</span> <span class="hl-op">=</span> <span class="hl-var">frame_size</span> <span class="hl-op">+</span> <span class="hl-num">10</span>
  <span class="hl-var">next_tag_length_remaining</span> <span class="hl-op">=</span> <span class="hl-var">tag_length_remaining</span> <span class="hl-op">-</span> <span class="hl-var">total_frame_size</span>
<span class="hl-kw">end</span>
</code></pre>
<p>After that, it hands the ID of the frame (a four character ASCII string), along with the size of the frame (not counting the header), and the remainder of the binary over to the <code>decode_frame</code> which will use the ID of the specific frame to decide how to decode the data.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-cmt"># ...</span>

  <span class="hl-var">result</span> <span class="hl-op">=</span> <span class="hl-fn">decode_frame</span>(<span class="hl-var">frame_id</span>, <span class="hl-var">frame_size</span>, <span class="hl-var">rest</span>)
<span class="hl-kw">end</span>
</code></pre><h2 id="decoding-frames"><a href="#decoding-frames" class="header-anchor" aria-hidden="true" role="presentation">##</a> Decoding Frames</h2>
<p>The <code>decode_frame</code> function pattern matches on the ID of the frame and the binary and does different things, as defined by the spec, depending on the frame. There are a few specific frames I’m interested in, so those are the ones that I’ll go into here, but the others could be easily parsed with the same techniques.</p>
<p>First off, the TXXX frame. This frame contains custom, user-defined key/value pairs of strings. It starts off with a number that defines the text encoding scheme.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_frame</span>(<span class="hl-str">&quot;TXXX&quot;</span>, <span class="hl-var">frame_size</span>, &lt;&lt;<span class="hl-var">text_encoding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;) <span class="hl-kw">do</span>
<span class="hl-kw">end</span>
</code></pre>
<p>The text encoding is a single byte containing 0, 1, 2, or 3. First off, the simple encodings: 0 represents an ISO-8859-1 string (byte-compatible with UTF-8, for our purposes) terminated by a null byte and 3 represents a UTF-8 string, also terminated by a null byte. These are very easy to handle, because strings in Erlang are just binaries, so we can return the data directly.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">convert_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">str</span>) <span class="hl-kw">when</span> <span class="hl-var">encoding</span> <span class="hl-kw">in</span> [<span class="hl-num">0</span>, <span class="hl-num">3</span>] <span class="hl-kw">do</span>
  <span class="hl-var">str</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Next, encoding 1 is text encoded as UTF-16 starting with the <a href="https://en.wikipedia.org/wiki/Byte_order_mark" data-link="en.wikipedia.org/wiki/Byte_order_mark">byte order mark</a> which lets programs detect the endianness of the text. Strings in this encoding are defined by ID3 to end with two null characters. The <code>bom_to_encoding</code> function in OTP checks if the given binary starts with the byte order mark, and, if so, returns the detected text encoding and endianness as well as the length of the BOM. These lengths lets us drop the BOM from the beginning of the data. We can then use another function in the <code>:unicode</code> module to convert the binary data to a regular UTF-8 string.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">convert_string</span>(<span class="hl-num">1</span>, <span class="hl-var">data</span>) <span class="hl-kw">do</span>
  {<span class="hl-var">encoding</span>, <span class="hl-var">bom_length</span>} <span class="hl-op">=</span> <span class="hl-mod">:unicode</span><span class="hl-op">.</span><span class="hl-fn">bom_to_encoding</span>(<span class="hl-var">data</span>)
  {<span class="hl-cmt">_</span>, <span class="hl-var">string_data</span>} <span class="hl-op">=</span> <span class="hl-mod">String</span><span class="hl-op">.</span><span class="hl-fn">split_at</span>(<span class="hl-var">data</span>, <span class="hl-var">bom_length</span>)
  <span class="hl-mod">:unicode</span><span class="hl-op">.</span><span class="hl-fn">characters_to_binary</span>(<span class="hl-var">string_data</span>, <span class="hl-var">encoding</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>Encoding 2 is also UTF-16, but always big-endian and without the byte order mark. It’s also terminated by two null bytes.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">convert_string</span>(<span class="hl-num">2</span>, <span class="hl-var">data</span>) <span class="hl-kw">do</span>
  <span class="hl-mod">:unicode</span><span class="hl-op">.</span><span class="hl-fn">characters_to_binary</span>(<span class="hl-var">data</span>, {<span class="hl-str">:utf16</span>, <span class="hl-str">:big</span>})
<span class="hl-kw">end</span>
</code></pre>
<p>The <code>convert_string</code> will take a piece of string data and convert it to something we can actually use as a string, but we still need to figure out where the string ends. When decoding a frame, we need to find all the data up to one or two null characters, depending on the encoding.</p>
<p>Unfortunately, a number of tracks in my library have text frames which specify a UTF-16 encoding but are actually malformed and don’t end with two null characters (they just run up to the end of the frame). So, the main decoding function is also going to take the maximum length of the frame so that we don’t accidentally run over the end.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>) <span class="hl-kw">when</span> <span class="hl-var">encoding</span> <span class="hl-kw">in</span> [<span class="hl-num">1</span>, <span class="hl-num">2</span>] <span class="hl-kw">do</span>
  {<span class="hl-var">str_data</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">get_double_null_terminated</span>(<span class="hl-var">data</span>, <span class="hl-var">max_byte_size</span>)
<span class="hl-kw">end</span>
</code></pre>
<p>We’ll write a function that scans through a binary looking for a sequential pair of null characters, or until it reaches the maximum length specified. In either of those cases, it will return the binary up until the null characters as well as the remainder of the binary. If neither of those conditions are met, it will skip two bytes from the beginning of the binary, prepending them to the accumulator, and recursively calling itself. We can safely skip two bytes when the double null sequence isn’t found because every UTF-16 character is represented by two bytes and therefore the double null terminator won’t cross the boundary between two characters.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">get_double_null_terminated</span>(<span class="hl-var">data</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">acc</span> <span class="hl-op">\\</span> [])

<span class="hl-kw">def</span> <span class="hl-fn">get_double_null_terminated</span>(<span class="hl-var">rest</span>, <span class="hl-num">0</span>, <span class="hl-var">acc</span>) <span class="hl-kw">do</span>
  {<span class="hl-var">acc</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">reverse</span>() <span class="hl-op">|&gt;</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">list_to_bin</span>(), <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">get_double_null_terminated</span>(&lt;&lt;<span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;, <span class="hl-cmt">_</span>, <span class="hl-var">acc</span>) <span class="hl-kw">do</span>
  {<span class="hl-var">acc</span> <span class="hl-op">|&gt;</span> <span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">reverse</span>() <span class="hl-op">|&gt;</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">list_to_bin</span>(), <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">get_double_null_terminated</span>(&lt;&lt;<span class="hl-var">a</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">b</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;, <span class="hl-var">max_byte_size</span>, <span class="hl-var">acc</span>) <span class="hl-kw">do</span>
  <span class="hl-var">next_max_byte_size</span> <span class="hl-op">=</span> <span class="hl-var">max_byte_size</span> <span class="hl-op">-</span> <span class="hl-num">2</span>
  <span class="hl-fn">get_double_null_terminated</span>(<span class="hl-var">rest</span>, <span class="hl-var">next_max_byte_size</span>, [<span class="hl-var">b</span>, <span class="hl-var">a</span> <span class="hl-op">|</span> <span class="hl-var">acc</span>])
<span class="hl-kw">end</span>
</code></pre>
<p>The decode function can then convert the string data to a regular, UTF-8 string and return it, along with how many bytes were consumed (so that the frame decoding can know how much of its length is remaining), and the remaining binary data.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>) <span class="hl-kw">when</span> <span class="hl-var">encoding</span> <span class="hl-kw">in</span> [<span class="hl-num">1</span>, <span class="hl-num">2</span>] <span class="hl-kw">do</span>
  {<span class="hl-var">str_data</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">get_double_null_terminated</span>(<span class="hl-var">data</span>, <span class="hl-var">max_byte_size</span>)

  {<span class="hl-fn">convert_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">str</span>), <span class="hl-fn">byte_size</span>(<span class="hl-var">str_data</span>) <span class="hl-op">+</span> <span class="hl-num">2</span>, <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>When decoding a UTF-8 string, we split the data at the first occurrence of the null byte and ensure that the size of whatever came before it is no greater than the max. If the size of the data up to the first null byte is greater than the max size, we just split the data at the maximum byte size, considering that to be the string. And once again, the function returns the string itself, the number of bytes consumed, and the remaining data.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>) <span class="hl-kw">when</span> <span class="hl-var">encoding</span> <span class="hl-kw">in</span> [<span class="hl-num">0</span>, <span class="hl-num">3</span>] <span class="hl-kw">do</span>
  <span class="hl-kw">case</span> <span class="hl-mod">:binary</span><span class="hl-op">.</span><span class="hl-fn">split</span>(<span class="hl-var">data</span>, &lt;&lt;<span class="hl-num">0</span>&gt;&gt;) <span class="hl-kw">do</span>
  	[<span class="hl-var">str</span>, <span class="hl-var">rest</span>] <span class="hl-kw">when</span> <span class="hl-fn">byte_size</span>(<span class="hl-var">str</span>) <span class="hl-op">+</span> <span class="hl-num">1</span> <span class="hl-op">&lt;=</span> <span class="hl-var">max_byte_size</span> <span class="hl-op">-&gt;</span>
	  {<span class="hl-var">str</span>, <span class="hl-fn">byte_size</span>(<span class="hl-var">str</span>) <span class="hl-op">+</span> <span class="hl-num">1</span>, <span class="hl-var">rest</span>}

	<span class="hl-cmt">_</span> <span class="hl-op">-&gt;</span>
	  {<span class="hl-var">str</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-mod">:erlang</span><span class="hl-op">.</span><span class="hl-fn">split_binary</span>(<span class="hl-var">data</span>, <span class="hl-var">max_byte_size</span>)
	  {<span class="hl-var">str</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">rest</span>}
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>Now, back to the TXXX frame. The description is decoded by calling our decode function with the text encoding from the frame, a maximum length of 1 less than the frame size (to account for the text encoding byte), and the data. It gives us back the value for the description string, how many bytes were consumed and the rest of the data. Then, from the <code>decode_frame</code> function we return a tuple of two elements: a tuple that represents the frame and the remaining binary data. The tuple that represents the frame is also two elements, the first of which is the frame ID and the second of which is the frame’s value (in this case, yet another tuple of the description and value strings) so that we can produce a nice map of all the frame data at the end.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_frame</span>(<span class="hl-str">&quot;TXXX&quot;</span>, <span class="hl-var">frame_size</span>, &lt;&lt;<span class="hl-var">text_encoding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;) <span class="hl-kw">do</span>
  {<span class="hl-var">description</span>, <span class="hl-var">desc_size</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span>, <span class="hl-var">rest</span>)
  {<span class="hl-var">value</span>, <span class="hl-cmt">_</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span> <span class="hl-op">-</span> <span class="hl-var">desc_size</span>, <span class="hl-var">rest</span>)
  {{<span class="hl-str">&quot;TXXX&quot;</span>, {<span class="hl-var">description</span>, <span class="hl-var">value</span>}}, <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>Next is the COMM frame, for user specified comments. It starts with a byte for the text encoding, then three bytes specifying a language code, then a string for a short description of the comment, then another for the full value of the comment.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_frame</span>(<span class="hl-str">&quot;COMM&quot;</span>, <span class="hl-var">frame_size</span>, &lt;&lt;<span class="hl-var">text_encoding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">language</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-num">3</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;) <span class="hl-kw">do</span>
  {<span class="hl-var">short_desc</span>, <span class="hl-var">desc_size</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">4</span>, <span class="hl-var">rest</span>)
  {<span class="hl-var">value</span>, <span class="hl-cmt">_</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">4</span> <span class="hl-op">-</span> <span class="hl-var">desc_size</span>, <span class="hl-var">rest</span>)
  {{<span class="hl-str">&quot;COMM&quot;</span>, {<span class="hl-var">language</span>, <span class="hl-var">short_desc</span>, <span class="hl-var">value</span>}}, <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>The other somewhat complex frame to decode is the APIC frame. It contains an image for the album artwork of the track. It starts with a byte for the text encoding of the description. Then there’s a null-terminated ASCII string which contains the MIME type of the image. It’s followed by a byte for the picture type (one of 15 spec-defined values which indicates what the picture represents). Then a string for the image description, and lastly the image data itself.</p>
<p>The only thing that’s different about how the APIC frame is decoded compared to the other frames we’ve seen is that there’s nothing in-band to signal the end of the picture data. It starts after the description ends and just keeps going until the end of the frame. So, we need to calculate the image’s size (i.e., the frame size minus the size of all the data that comes before the image) and then just take that many bytes from the stream.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_frame</span>(<span class="hl-str">&quot;APIC&quot;</span>, <span class="hl-var">frame_size</span>, &lt;&lt;<span class="hl-var">text_encoding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;) <span class="hl-kw">do</span>
  {<span class="hl-var">mime_type</span>, <span class="hl-var">mime_len</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-var">decode_string</span>(<span class="hl-num">0</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span>, <span class="hl-var">rest</span>}

  &lt;&lt;<span class="hl-var">picture_type</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; = <span class="hl-var">rest</span>

  {<span class="hl-var">description</span>, <span class="hl-var">desc_len</span>, <span class="hl-var">rest</span>} = <span class="hl-fn">decode_string</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span> <span class="hl-op">-</span> <span class="hl-var">mime_len</span> <span class="hl-op">-</span> <span class="hl-num">1</span>, <span class="hl-var">rest</span>)

  image_data_size = <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span> <span class="hl-op">-</span> <span class="hl-var">mime_len</span> <span class="hl-op">-</span> <span class="hl-num">1</span> <span class="hl-op">-</span> <span class="hl-fn">desc_len</span>
  {<span class="hl-var">image_data</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-mod">:erlang</span><span class="hl-op">.</span><span class="hl-fn">split_binary</span>(<span class="hl-var">rest</span>, <span class="hl-var">image_data_size</span>)

  {{<span class="hl-str">&quot;APIC&quot;</span>, {<span class="hl-var">mime_type</span>, <span class="hl-var">picture_type</span>, <span class="hl-var">description</span>, <span class="hl-var">image_data</span>}}, <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>
</code></pre>
<p>Now, for the default case of the <code>decode_frame</code> function. There are a couple things this first needs to handle. First is text information frames. These are a class of frames whose IDs start with the letter T and are followed by three alpha-numeric (uppercase) characters. Each of these frames follows the same format and contains just a text encoding indicator and one or more strings.</p>
<aside>
<p>Originally, I tried to handle text information frames the same way I had with other frame types, with just a when condition for the frame ID parameter on a case of the function. It turns out function guards that check if a parameter is an element of a list work by generating a version of the function that matches each specific value. So, trying to check if a param was a valid text information frame ID meant check if it was an element of a 36^3 = 46,656 element long list. 46,656 individual function cases were generated, and the Elixir compiler took almost 10 minutes to compile just that file. And it crashed inside BEAM when I actually tried to run it.</p>
</aside>
<p>There are also the many, many frames which we have not specifically handled. Even if we don’t do anything with them, the decoder still needs to be aware of their existence, because if it encounters a frame that it can’t do anything with, it needs to skip over it. Otherwise, the decoder would halt upon encountering the first frame of an unhandled type, potentially missing subsequent frames that we do care about. To handle this, I took the list of all declared frames from the <a href="https://id3.org/id3v2.4.0-frames" data-link="id3.org/id3v2.4.0-frames">ID3 native frames</a> spec and copied it into a constant list that I can then check potential IDs against.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_frame</span>(<span class="hl-var">id</span>, <span class="hl-var">frame_size</span>, <span class="hl-var">rest</span>) <span class="hl-kw">do</span>
  <span class="hl-kw">cond</span> <span class="hl-kw">do</span>
    <span class="hl-mod">Regex</span><span class="hl-op">.</span><span class="hl-fn">match?</span>(<span class="hl-str">~r<span class="hl-str">/</span>^T[0-9A-Z]+$<span class="hl-str">/</span></span>, <span class="hl-var">id</span>) <span class="hl-op">-&gt;</span>
	  <span class="hl-fn">decode_text_frame</span>(<span class="hl-var">id</span>, <span class="hl-var">frame_size</span>, <span class="hl-var">rest</span>)

	<span class="hl-var">id</span> <span class="hl-kw">in</span> <span class="hl-attr">@</span><span class="hl-attr">declared_frame_ids</span> <span class="hl-op">-&gt;</span>
	  &lt;&lt;<span class="hl-cmt">_frame_data</span><span class="hl-op">::</span><span class="hl-var">binary</span><span class="hl-op">-</span><span class="hl-fn">size</span>(<span class="hl-var">frame_size</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt; <span class="hl-op">=</span> <span class="hl-var">rest</span>
	  {<span class="hl-const">nil</span>, <span class="hl-var">rest</span>, <span class="hl-str">:cont</span>}

	<span class="hl-const">true</span> <span class="hl-op">-&gt;</span>
	  {<span class="hl-const">nil</span>, <span class="hl-var">rest</span>, <span class="hl-str">:halt</span>}
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>If it encounters a text information frame, it delegates to another function which handles pulling out the actual value. Text information frames also have a slight difference: their values can be a list of multiple null-terminated strings. So, this function calls <code>decode_string_sequence</code> which decodes as many null-terminated strings as it can up until it reaches the end of the frame.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">decode_text_frame</span>(<span class="hl-var">id</span>, <span class="hl-var">frame_size</span>, &lt;&lt;<span class="hl-var">text_encoding</span><span class="hl-op">::</span><span class="hl-fn">size</span>(<span class="hl-num">8</span>), <span class="hl-var">rest</span><span class="hl-op">::</span><span class="hl-var">binary</span>&gt;&gt;) <span class="hl-kw">do</span>
  {<span class="hl-var">strs</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string_sequence</span>(<span class="hl-var">text_encoding</span>, <span class="hl-var">frame_size</span> <span class="hl-op">-</span> <span class="hl-num">1</span>, <span class="hl-var">rest</span>)
  {{<span class="hl-var">id</span>, <span class="hl-var">strs</span>}, <span class="hl-var">rest</span>}
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">decode_string_sequence</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>, <span class="hl-var">acc</span> <span class="hl-op">\\</span> [])

<span class="hl-kw">def</span> <span class="hl-fn">decode_string_sequence</span>(<span class="hl-cmt">_</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>, <span class="hl-var">acc</span>) <span class="hl-kw">when</span> <span class="hl-var">max_byte_size</span> <span class="hl-op">&lt;=</span> <span class="hl-num">0</span> <span class="hl-kw">do</span>
  {<span class="hl-mod">Enum</span><span class="hl-op">.</span><span class="hl-fn">reverse</span>(<span class="hl-var">acc</span>), <span class="hl-var">data</span>}
<span class="hl-kw">end</span>

<span class="hl-kw">def</span> <span class="hl-fn">decode_string_sequence</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>, <span class="hl-var">acc</span>) <span class="hl-kw">do</span>
  {<span class="hl-var">str</span>, <span class="hl-var">str_size</span>, <span class="hl-var">rest</span>} <span class="hl-op">=</span> <span class="hl-fn">decode_string</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span>, <span class="hl-var">data</span>)
  <span class="hl-fn">decode_string_sequence</span>(<span class="hl-var">encoding</span>, <span class="hl-var">max_byte_size</span> <span class="hl-op">-</span> <span class="hl-var">str_size</span>, <span class="hl-var">rest</span>, [<span class="hl-var">str</span> <span class="hl-op">|</span> <span class="hl-var">acc</span>])
<span class="hl-kw">end</span>
</code></pre>
<p>If the frame ID is valid, but it wasn’t already handled, it simply skips over the data for that frame. It returns <code>nil</code> for the frame itself, and adds a third element to the returned tuple. This is an atom, either <code>:cont</code> or <code>:halt</code> which signals to the main <code>parse_frames</code> loop whether it should keep going or stop when no frame is found.</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-kw">def</span> <span class="hl-fn">parse_frames</span>(<span class="hl-var">...</span>) <span class="hl-kw">do</span>
  <span class="hl-cmt"># ...</span>

  <span class="hl-var">result</span> <span class="hl-op">=</span> <span class="hl-fn">decode_frame</span>(<span class="hl-var">frame_id</span>, <span class="hl-var">frame_size</span>, <span class="hl-var">rest</span>)

  <span class="hl-kw">case</span> <span class="hl-var">result</span> <span class="hl-kw">do</span>
    {<span class="hl-const">nil</span>, <span class="hl-var">rest</span>, <span class="hl-str">:halt</span>} <span class="hl-op">-&gt;</span>
      {<span class="hl-mod">Map</span><span class="hl-op">.</span><span class="hl-fn">new</span>(<span class="hl-var">frames</span>), <span class="hl-var">rest</span>}

    {<span class="hl-const">nil</span>, <span class="hl-var">rest</span>, <span class="hl-str">:cont</span>} <span class="hl-op">-&gt;</span>
      <span class="hl-fn">parse_frames</span>(<span class="hl-var">major_version</span>, <span class="hl-var">rest</span>, <span class="hl-var">next_tag_length_remaining</span>, <span class="hl-var">frames</span>)

    {<span class="hl-var">new_frame</span>, <span class="hl-var">rest</span>} <span class="hl-op">-&gt;</span>
      <span class="hl-fn">parse_frames</span>(<span class="hl-var">major_version</span>, <span class="hl-var">rest</span>, <span class="hl-var">next_tag_length_remaining</span>, [<span class="hl-var">new_frame</span> <span class="hl-op">|</span> <span class="hl-var">frames</span>])
  <span class="hl-kw">end</span>
<span class="hl-kw">end</span>
</code></pre>
<p>After attempting to decode a frame, the <code>parse_frames</code> function matches on the result of the decode attempt. If the frame ID was not valid and it’s instructed to halt, it creates a map from the list of frames and returns it along with the remainder of the data. If there was a valid frame, but nothing was decoded from it, it recurses, calling <code>parse_frames</code> again with whatever data came after the skipped frame. If there was a frame, it adds it to the list and and again recurses.</p>
<p>And with that, we can finally have enough to parse the ID3 data from a real live MP3 file and get out, maybe not all the information, but the parts I care about:</p>
<pre class="highlight" data-lang="elixir"><code><span class="hl-var">iex</span><span class="hl-op">&gt;</span> <span class="hl-var">data</span> <span class="hl-op">=</span> <span class="hl-mod">File</span><span class="hl-op">.</span><span class="hl-fn">read!</span>(<span class="hl-str">&quot;test.mp3&quot;</span>)
<span class="hl-var">iex</span><span class="hl-op">&gt;</span> <span class="hl-mod">ID3</span><span class="hl-op">.</span><span class="hl-fn">parse_tag</span>(<span class="hl-var">data</span>)
{
  %{
    <span class="hl-str">&quot;APIC&quot;</span> <span class="hl-op">=&gt;</span> {<span class="hl-str">&quot;image/jpeg&quot;</span>, <span class="hl-num">3</span>, <span class="hl-str">&quot;cover&quot;</span>, &lt;&lt;<span class="hl-var">...</span>&gt;&gt;},
    <span class="hl-str">&quot;COMM&quot;</span> <span class="hl-op">=&gt;</span> {<span class="hl-str">&quot;eng&quot;</span>, <span class="hl-str">&quot;&quot;</span>, <span class="hl-str">&quot;Visit http://justiceelectro.bandcamp.com&quot;</span>},
    <span class="hl-str">&quot;TALB&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;Woman Worldwide&quot;</span>,
    <span class="hl-str">&quot;TIT2&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;Safe and Sound (WWW)&quot;</span>,
    <span class="hl-str">&quot;TPE1&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;Justice&quot;</span>,
    <span class="hl-str">&quot;TPE2&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;Justice&quot;</span>,
    <span class="hl-str">&quot;TRCK&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;1&quot;</span>,
    <span class="hl-str">&quot;TYER&quot;</span> <span class="hl-op">=&gt;</span> <span class="hl-str">&quot;2018&quot;</span>
  },
  &lt;&lt;<span class="hl-var">...</span>&gt;&gt;
}
</code></pre>
<p>One of the pieces of information I was hoping I could get from the ID3 tags was the durations of the MP3s in my library. But alas, none of the tracks I have use the TLEN frame, so it looks like I’ll have to try and pull that data out of the MP3 myself. But that’s a post for another time…</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Actual, DRM-free files because music streaming services by and large don’t pay artists fairly<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>. MP3s specifically because they Just Work everywhere, and I cannot for the life of me hear the difference between a 320kbps MP3 and an &lt;insert audiophile format of choice&gt; file. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Spotify pays artists 0.38¢ per play and Apple Music pays 0.783¢ per play (<a href="https://help.songtrust.com/knowledge/what-is-the-pay-rate-for-spotify-streams" data-link="help.songtrust.com/knowledge/what-is-the…">source</a>). For an album of 12 songs that costs $10 (assuming wherever you buy it from takes a 30% cut), you would have to listen all the way through it between 75 and 150 times for the artist to receive as much money as if you had just purchased the album outright. That’s hardly fair and is not sustainable for all but the largest of musicians. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Automatically Scroll the Text View Caret into View</title>
      <link>https://shadowfacts.net/2020/swiftui-text-view-caret/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/swiftui-text-view-caret/</guid>
      <pubDate>Wed, 11 Nov 2020 14:38:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>That’s right, it’s time for this month’s installment of the <a href="/2020/swiftui-expanding-text-view/" data-link="/2020/swiftui-expanding-text-view/">never</a> <a href="/2020/more-swiftui-text-views/" data-link="/2020/more-swiftui-text-views/">ending</a> SwiftUI text view saga! The text view previously implemented is of course auto-expanding and has scrolling disabled. While this mostly works, it has a rather unfortunate UX problem. Let’s say the user is typing into the text view, and they reach the end of the screen. As they continue to type, the text will wrap onto the next line and the caret will go with it. But, because they’re already at the bottom of the screen (or immediately above the bottom of the keyboard), the caret, along with the text that they’re currently typing, will no longer be visible. </p>
<!-- excerpt-end -->
<p>Ordinarily, UITextView would handle this for us. Whenever the cursor moves, it would simply scroll itself so that the cursor is visible. But because the UITextView expands to show its entire contents, the cursor never occluded by the text view’s bounds. So, we need to handle it ourselves.</p>
<p>Using the <code>textViewDidChangeSelection</code> delegate method, we can tell whenever the cursor is moved.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> WrappedTextView: <span class="hl-type">UIViewRepresentable</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">func</span> textViewDidChangeSelection(<span class="hl-kw">_</span> textView: <span class="hl-type">UITextView</span>) {
			<span class="hl-fn">ensureCursorVisible</span>(textView: textView)
		}
	}
}
</code></pre>
<p>To actually make the cursor visible, we need a two key things: the scroll view that we’re actually going to scroll, and the position of the cursor on screen.</p>
<p>Getting the scroll view instance is fairly straightforward. Since we have the wrapped <code>UITextView</code> from the delegate method, we can just walk up the view hierarchy until we find a <code>UIScrollView</code>. This does technically rely on an internal implementation detail of SwiftUI’s <code>ScrollView</code> (unless you’re also using <code>UIViewRepresentable</code> to wrap the scroll view yourself), namely that it’s backed by an actual UIKit scroll view, but this is one that I’m willing to live with, given that it doesn’t seem likely to change any time soon. We can also fail gracefully if the scroll view isn’t found, to try and prevent any future catastrophic breakage.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> findParentScrollView(of view: <span class="hl-type">UIView</span>) -&gt; <span class="hl-type">UIScrollView</span>? {
		<span class="hl-kw">var</span> current = view
		<span class="hl-kw">while let</span> superview = current.<span class="hl-fn">superview</span> {
			<span class="hl-kw">if let</span> scrollView = superview <span class="hl-kw">as</span>? <span class="hl-type">UIScrollView</span> {
				<span class="hl-kw">return</span> scrollView
			} <span class="hl-kw">else</span> {
				current = superview
			}
		}
		<span class="hl-kw">return nil</span>
	}
}
</code></pre>
<p>Now, getting the scroll view is as simple as calling this method with the text view:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-kw">guard let</span> scrollView = <span class="hl-fn">findParentScrollView</span>(of: textView) <span class="hl-kw">else</span> {
			<span class="hl-kw">return</span>
		}
	}
}
</code></pre>
<p>Next, we need to get the cursor’s position. We can use the <code>selectedTextRange</code> property of <code>UITextView</code> and its <code>carectRect(for:)</code> method. Note that this is the <code>seelcted<em>Text</em>Range</code> property, not the <code>selectedRange</code> property. The text range version gives us a <code>UITextRange</code> which uses <code>UITextPosition</code>s for its start and end locations, in constrast with <code>selectedRange</code> which is just a regular <code>NSRange</code> of integers. <code>UITextPosition</code> is an opaque class, subclasses of which are used internally by objects conforming to the <code>UITextInput</code> protocol and is what’s accepted by the <code>caretRect</code> method.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-kw">guard let</span> scrollView = <span class="hl-fn">findParentScrollView</span>(of: textView),
		      <span class="hl-kw">let</span> range = textView.<span class="hl-prop">selectedTextRange</span> <span class="hl-kw">else</span> {
			<span class="hl-kw">return</span>
		}

		<span class="hl-kw">let</span> cursorRect = textView.<span class="hl-fn">carectRect</span>(for: range.<span class="hl-prop">start</span>)
	}
}
</code></pre>
<p>The <code>carectRect(for:)</code> method returns the rect representing the cursor, but in the coordinate space of the text view. In order to actually scroll with it, we need to convert it into the scroll view’s coordinate space. After that, we can use the <code>UIScrollView.scrollRectToVisible</code> method to actually change the content offset of the scroll view so that the cursor is visible, without having to do a bunch of tedious math ourselves.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">var</span> rectToMakeVisible = textView.<span class="hl-fn">convert</span>(cursorRect, to: scrollView)
		scrollView.<span class="hl-fn">scrollRectToVisible</span>(rectToMakeVisible, animated: <span class="hl-kw">true</span>)
	}
}
</code></pre>
<p>This works pretty well: when the user is typing and the cursor wraps onto a new line, the scroll view scrolls so that the cursor is back on-screen. However, there are a couple things that could be improved.</p>
<p>First off, the area of the text view that’s being made visible is limited to exactly the size and position of the cursor. While this does mean the cursor becomes visible, it’s at the very bottom of the screen, and only ever so slightly above the keyboard, which doesn’t look great. It would be better if, after scrolling, there was a little bit of space between the edge of the screen and the line of text being edited. We can accomplish this by adjusting the rectangle we want to make visible.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">var</span> rectToMakeVisible = textView.<span class="hl-fn">convert</span>(cursorRect, to: scrollView)

		rectToMakeVisible.<span class="hl-prop">origin</span>.<span class="hl-prop">y</span> -= cursorRect.<span class="hl-prop">height</span>
		rectToMakeVisible.<span class="hl-prop">size</span>.<span class="hl-prop">height</span> *= <span class="hl-num">3</span>

		scrollView.<span class="hl-fn">scrollRectToVisible</span>(rectToMakeVisible, animated: <span class="hl-kw">true</span>)
	}
}
</code></pre>
<p>By moving the Y coordinate of the rect up by the cursor’s height and tripling the rect’s height, we change the rect so it’s centered on the cursor and there’s vertical padding on either side equal to the height of the cursor. Scrolling this rect into view will make sure that the cursor isn’t all the way at the top or bottom of the screen, but instead has some room to breathe.</p>
<p>The other issue is that if the user is typing very quickly and the cursor is changing lines rapidly, the scroll view’s offset will stop animating smoothly and start jittering around. This is because we’re calling <code>scrollRectToVisible</code> with animation enabled many times in quick succession. Multiple animations end up running at simultaneously and are competing for which gets to control the content offset. This is slightly more complicated to fix.</p>
<p>We need some way of controlling when the animation is running, so that whenever we’re about to start a new animation, we can cancel the old one so that it doesn’t interfere. While passing <code>animated: true</code> to when scrolling the scroll view, there’s no easy way of doing this, since we don’t know how the scroll view is actually performing the animation internally. We need to control the animation entirely ourselves. We can do this using a <a href="https://developer.apple.com/documentation/uikit/uiviewpropertyanimator" data-link="developer.apple.com/documentation/uikit/…"><code>UIViewPropertyAnimator</code></a>, which allows cancelling an in-progress animation.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-cmt">// ...</span>

		<span class="hl-kw">let</span> animator = <span class="hl-type">UIViewPropertyAnimator</span>(duration: <span class="hl-num">0.1</span>, curve: .<span class="hl-prop">linear</span>) {
			scrollView.<span class="hl-fn">scrollRectToVisible</span>(rectToMakeVisible, animated: <span class="hl-kw">false</span>)
		}
		animator.<span class="hl-fn">startAnimation</span>()
	}
}
</code></pre>
<p>By passing <code>false</code> for the <code>animated:</code> parameter of <code>scrollRectToVisible</code>, we instruct it to update the scroll view’s <code>contentOffset</code> immediately, which will be captured by the property animator, and then animated smoothly. I used a duration of a tenth of a second and a linear curve, because it felt quite natural and didn’t cause a problem when typing quickly. (Using a non-linear curve could cause a problem, because as animations are rapidly started and stopped, the velocity of the animation would not stay constant. Given such a short duration, this may not be a problem, but, also given the short duration, the effects of a non-linear curve aren’t really visible.)</p>
<p>We can then store the animator on the coordinator class so that when we next try to start an animation, we can cancel the any existing, non-completed one:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private var</span> cursorScrollPositionAnimator: <span class="hl-type">UIViewPropertyAnimator</span>?

	<span class="hl-kw">private func</span> ensureCursorVisible(textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-cmt">// ...</span>

		<span class="hl-kw">if let</span> existing = <span class="hl-kw">self</span>.<span class="hl-prop">cursorScrollPositionAnimator</span> {
			existing.<span class="hl-fn">stopAnimation</span>(<span class="hl-kw">false</span>)
		}

		<span class="hl-kw">let</span> animator = <span class="hl-type">UIViewPropertyAnimator</span>(duration: <span class="hl-num">0.1</span>, curve: .<span class="hl-prop">linear</span>) {
			scrollView.<span class="hl-fn">scrollRectToVisible</span>(rectToMakeVisible, animated: <span class="hl-kw">false</span>)
		}
		animator.<span class="hl-fn">startAnimation</span>()
		<span class="hl-kw">self</span>.<span class="hl-prop">cursorScrollPositionAnimator</span> = animator
	}
}
</code></pre>
<p>The <code>stopAnimation</code> method takes a parameter called <code>withoutFinishing</code> for which we pass <code>false</code> because we want the in-progress animation to stop immediately where it is, without jumping to the end (this also skips calling any completion blocks of the animator, but this doesn’t matter as we’re not using any).</p>
<p>With that, it finally works correctly. As the user is typing, the scroll view is scrolled smoothly to keep the cursor in view at all times, and there are no issues with the user typing too quickly for the animations to keep up.</p>
]]></content:encoded>
    </item>
    <item>
      <title>A UI Framework Dilemma</title>
      <link>https://shadowfacts.net/2020/ui-framework-dilemma/</link>
      <category>swift</category>
      <category>gemini</category>
      <guid>https://shadowfacts.net/2020/ui-framework-dilemma/</guid>
      <pubDate>Mon, 05 Oct 2020 20:17:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>For the past couple of weeks I’ve been building Rocketeer, an iOS browser for the <a href="https://gemini.circumlunar.space" data-link="gemini.circumlunar.space">Gemini</a> network.<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup> The gemtext format is very minimal, so I thought it would be fairly easy to build something to render Gemini documents. The format is line-oriented and only allows a few different line types. There are regular paragraphs, link lines, 3 levels of headings, unordered list items, preformatted blocks, and block quotes. All of these are pretty simple, visually speaking, and the layout is also straightforward. So, I expected to be able to build a renderer quite easily. Unfortunately, there turned out to be lots of little details that were not so obvious at first and introduced a bunch of complications.</p>
<!-- excerpt-end -->
<p>Initially, all of the UI code for Rocketeer was written using SwiftUI (and so it remains in the current (at the time of publication) <a href="https://testflight.apple.com/join/LAs1URxM" data-link="testflight.apple.com/join/LAs1URxM">public beta</a>). Originally I chose SwiftUI because it allowed me to build a functional document renderer incredibly quickly, so I could at least see something on screen. But, this worked out better, architecturally speaking, than I expected. Gemtext is a line-oriented format, so each line of text in the source document pretty much maps to a single visual unit for the display purposes. For the same reason, layout is quite simple. Just as the source document is a just list of lines arranged vertically, the rendered document is a list of display units arranged vertically, one after the other. With SwiftUI, the actual layout code is a simple as a single <code>VStack</code> (or <code>LazyVStack</code> on iOS 14, which gets you a simple layout with lazily initialized views without having to screw around with the table or collection view APIs that weren’t designed for such a thing) containing a <code>ForEach</code> that iterates over each of the display blocks. Put that inside a <code>ScrollView</code>, and bam—you’re rendering a whole Gemini document.</p>
<p>This was all well and good, until I realized there were a few features I wanted to add that SwiftUI wasn’t capable of. At first it was just custom context menu link previews (similar to what Safari does).</p>
<p>While SwiftUI does provide the <code>.contextMenu</code> modifier for adding context menu <em>actions</em> to a view, it doesn’t have a native API for creating custom context menu previews (FB8772786).  This could in theory be accomplished with a custom <code>UIViewRepresentable</code> that wraps a SwiftUI view in a <code>UIView</code> with a <code>UIContextMenuInteraction</code>, thereby granting access to the regular UIKit context menu APIs, but that’s a great deal of work a feature that’s small enough it probably wouldn’t be missed.</p>
<p>But, that wasn’t the end. I realized text selection would be a very useful feature. Replies to gemlog posts are commonly written by block-quoting parts of the original post and then writing the response below it (à la email bottom-posting). Imagine being able to read a Gemini post on an iPad, copying parts of it into a text editor to compose a response, and then uploading it to your server with an FTP app. That sounds like a pretty comfy workflow. One which requires the ability to select and copy text out of the browser. Which SwiftUI’s <code>Text</code> view can’t do (FB8773449).</p>
<p>I put aside text selection as something to revisit later. And then I got to thinking about interesting features that the Gemini format itself would facilitate. The first one that came to mind was generating a table of contents. As a side of a document format that doesn’t allow custom layout, documents on Geminispace use markup much more structurally/semantically. Headings are used as, well, headings. And having documents be well-ordered in addition to having three distinct levels of headings, means there’s an structure implied by the heading lines. By scanning through a document and looking at the heading lines, you could quite easily generate a table of contents for the entire document. Now, here’s where SwiftUI comes into this: If you’ve got a table of contents, you probably want to be able to skip to a specific point in it (what use would the table of contents be in a book without page numbers?). iOS 14 introduced <a href="https://developer.apple.com/documentation/swiftui/scrollviewreader" data-link="developer.apple.com/documentation/swiftu…"><code>ScrollViewReader</code></a>, which, allows the scroll view’s position to be manipulated programatically by jumping to specific views (despite the name, it does not do any reading of the <code>ScrollView</code>). Of course, this is only available on iOS 14, so any users on iOS 13 wouldn’t be able to use it. And given that iOS 14 was released less than a month ago, and how simple this feature seems, I didn’t want to make it dependent on a new OS version.</p>
<p>Also on the subject of scroll views, the browser should be able to persist the scroll view’s offset. If the user leaves the app and returns, they should be looking at the same point on the page as when they left. Likewise if they navigate backwards/forwards or switch tabs. This isn’t possible at all using SwiftUI’s <code>ScrollView</code>. I briefly tried setting up a <code>UIScrollView</code> myself, and then adding the <code>UIHostingController</code>’s view a child of it, but this completely removed the benefit of <code>LazyVStack</code>, causing abysmal performance when viewing some pages.</p>
<p>Even if wrapping <code>UIScrollView</code> myself had worked, what would be the point? Along with all the other things, I’d have written almost the entire Gemini document viewer using UIKit with only the teensiest bit of SwiftUI glue code. Why not then just go one step further and only use UIKit?</p>
<p>And this is entirely putting aside the fact that Rocketeer was originally intended to be a Mac app, with the iOS app as an afterthought when I realized it was easily possible since I was using SwiftUI. Using UIKit for so many integral parts would have meant huge portions of the codebase had to be rewritten for the macOS version.</p>
<p>So, while any one of these features wouldn’t be enough to get me to abandon SwiftUI, altogether, it’s enough to get me to start eyeing other options. Because to not do so would leave a lot of useful features on the table. The two likely replacements I came up with were: A) converting the Gemini document into an <code>NSAttributedString</code> and stuffing it into a <code>UITextView</code> or B) rendering the Gemini document to HTML and displaying it with a <code>WKWebView</code>. The following table is what what features I want for Rocketeer and with which options they’re possible.</p>
<table><thead><tr><th></th><th>SwiftUI</th><th>UITextView</th><th>WKWebView</th></tr></thead><tbody>
<tr><td>Render all Gemini line types</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Text selection</td><td>No</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Text selection between blocks</td><td>N/A</td><td>No</td><td>Yes</td></tr>
<tr><td>Context menu actions</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Context menu previews</td><td>Hacky</td><td>Yes</td><td>Yes</td></tr>
<tr><td>VoiceOver &amp; Voice Control</td><td>No (iOS bug)</td><td>?</td><td>Yes</td></tr>
<tr><td>Persist scroll position</td><td>No</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Scroll to anchor</td><td>iOS 14</td><td>Yes</td><td>Yes</td></tr>
<tr><td>Horizontally scrolling blocks</td><td>Yes</td><td>No</td><td>Yes</td></tr>
<tr><td>SF Symbols</td><td>Yes</td><td>Yes</td><td>Hacky</td></tr>
<tr><td>System fonts</td><td>Yes</td><td>Yes</td><td>Hacky</td></tr>
<tr><td>Block quote leading border</td><td>Yes</td><td>No</td><td>Yes</td></tr>
</tbody></table>
<p>Clearly, SwiftUI poses the most problems, and WebKit has the most possibilities. But plain old UIKit with a <code>UITextView</code> is in an annoying middle ground. A fair number of additional features are possible when compared to SwiftUI. But in exchange for that, it also <em>loses</em> some features that are possible with SwiftUI. And of course, there are still a few things that neither SwiftUI nor <code>UITextView</code> support.</p>
<p>First up: VoiceOver and Voice Control. While reading the contents of a text view with VoiceOver is obviously possible, there are still a few questions. The ideal narration behavior for Rocketeer would be to have VoiceOver reach each visual segment one at a time. One-by-one, going through each paragraph and link and list item<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>. As for Voice Control, the user needs to be able to interact with links within the text view individually. And in addition to the bare numbers all buttons are assigned, users should be able to speak the label of links to simulate tapping on them. I would hope UIKit provides suitable accessibility APIs for this, but I haven’t investigated it. I can’t imagine it’s as simple as using a single <code>Button</code> per link in SwiftUI. With <code>WKWebView</code>, these are not only possible but are handled automatically and completely for free, thanks to all the work the WebKit team has put into it.</p>
<p>Then there’s the issue of styling block quotes. The appearance I prefer is having the text be a lighter color and italicized, as well as having it slightly inset from the leading edge and have a solid border along the leading edge as well. As is becoming a pattern, with SwiftUI, this is fairly straightforward. You can use an <code>HStack</code> with some spacing containing a <code>Color</code> that has a frame of a fixed with and then the <code>Text</code>. The text will force the stack to expand vertically, and the color view will expand to fill the entire available height. This is also possible with CSS, using only the <code>border</code> and <code>padding</code> properties. <code>UITextView</code> of course makes things more complicated. While there may be an <code>NSAttributedString</code> attribute to indent an entire paragraph, there is no good way of applying a border to just a small part of a text view’s contents. A solution could be devised, by adding <code>UIView</code>s with background colors as subviews of the text view. But that has to make sure the border views are positioned correctly, and that they’re kept in sync with the text view as the device is rotated or the window resized. I can also imagine a truly cursed solution that works by performing word wrapping at the desired with, and then inserting a newline character, a special Unicode character that renders as a solid block, and some spaces at each point where the text would wrap at the desired width. Even with the block characters correctly positioned horizontally, there would likely be small gaps in between them vertically due to the font’s line spacing. Furthermore, you would have to keep this in sync with viewport size changes, and at any rate, this is just too cursed of a solution for me.</p>
<p>On to the subject of preformatted text, for which the challenge is that line wrapping needs to be disabled. Otherwise, certain preformatted text, like code, would be much more difficult to read. And even worse, ASCII art would be entirely illegible (and potentially take up a huge amount of vertical space unnecessarily, depending on how wide it is). With line wrapping disabled, the preformatted text needs to scroll horizontally so that it is all visible. But the entire document viewport shouldn’t scroll because it’s likely that the majority of the text is just going to be regular paragraphs, and moving the entire viewport horizontally would leave those off the screen. So, only the actual preformatted sections should be able to scroll horizontally, everything else should be fixed to the width of the screen. With SwiftUI, this is pretty straightforward: there’s just a <code>Text</code> view inside a horizontal <code>ScrollView</code> and that takes care of everything. Using WebKit for this is also very straightforward, since you can use CSS to set the <code>overflow-x</code> property on <code>&lt;pre&gt;</code> elements to make them scroll. When you want to use <code>UITextView</code> is where this gets complicated. This isn’t possible just with an attributed string and a plain old text view. You could work around this by adding horizontal another <code>UITextView</code> that’s configured to disable line wrapping and allow scrolling on the X-axis as a subview of the outer text view. But then you once again would have to deal with manually positioning the inner text views inside of the scroll view content of the outer text view and keeping that position in sync outer view changes size. You also have to somehow add spacing to the contents of the outer text view so that there’s an appropriately sized gap in its contents where the inner text view sits. This approach would also introduce problems for text selection.</p>
<p>While <code>UITextView</code> does support at least some amount of text selection, which is an improvement over SwiftUI’s complete lack thereof, it doesn’t support selecting text between multiple separate text views. Most of the time, this isn’t a big deal. But what if you want to copy a large chunk of text spanning multiple paragraphs, and say, a preformatted block. That wouldn’t be possible. If you were inserting preformatted blocks using the technique described in the previous paragraph, what would happen when you tried to make a selection that crosses the boundary between a piece of preformatted text and regular body text? The selection certainly wouldn’t continue between them smoothly, as the user would expect. If you had to insert extra text into the outer text view’s contents in order to make space for the inner views, starting a selection in the outer view and dragging across the inner view would just end up selecting the placeholder characters you inserted, which are not actually part of the source document. And if the user started a selection in one of the inner text views, dragging across the boundary into the outer text view would result in the selection just stopping abruptly when it reached the end of the preformatted text. Inserting <code>NSTextAttachment</code>s into the text as I previously described would also make the matter of selection more complicated. I use SF Symbols images as icons to show additional information about links (specifically, whether they’re pointing to the same domain, a different part of Geminispace, or another protocol altogether). <code>NSTextAttachment</code> can contain arbitrary <code>UIImage</code>s, so this is possible, but it makes the image a part of the text, meaning the user could end up making a selection that contains an attachment and copying it out of the app. What would happen then, you wonder? I don’t know, but I can’t I imagine it would be something helpful. Bullet points have a similar problems, since the <code>U+2022</code> character is inserted directly into the attributed string when rendering list item lines. <code>WKWebView</code> doesn’t have this problem, once again thanks to the  efforts of the WebKit team. Text selection across multiple HTML elements? No problem. Skip over decorative images? Sure thing. Bullet points? You bet.</p>
<p>Having gotten this far, you might think that using a <code>WKWebView</code> with the gemtext converted into HTML is the perfect solution. But of course, there are a couple regressions when going from plain old UIKit to WebKit, since nothing could ever be simple.</p>
<p>The first is the issue of SF Symbols. Although each SF Symbol does have a character code allocated from a resaved section of Unicode, none of the system fonts accessible from the web view will render the symbol, so you’ll just end up with a box. The images (or SVGs) for individual SF Symbols can be extracted from system fonts, and the content of a WKWebView does theoretically have a way of accessing resources bundled with the app, so in theory they could be displayed. But who knows if that would get past App Review.</p>
<p>There’s a similar problem with fonts. I hadn’t mentioned it, but the font I used for both the SwiftUI and <code>UITextView</code> versions of this has been Apple’s New York, which is the system-provided serifed font. This is no problem for SwiftUI and UIKit, since their font classes both have methods for getting the system font of a certain design. But, as far as I can tell, these system fonts are not accessible from web content. Even using the internal name, <code>.NewYork-Regular</code> doesn’t work; it just falls back on the browser’s default font. A similar approach may be taken to the SF Symbols issue, since Apple does make their system fonts available for download on their developer website<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>. The font could be bundled with the app and then loaded from the web content, though again, who knows how this would go over with App Review.</p>
<p>So, after all that, what am I going to do for Rocketeer. Well, from a customer perspective, the <code>WKWebView</code> solution is clearly the best since it both allows far more features and makes a number of others behave much more inline with the way you’d expect. But I’m kinda annoyed about it. This isn’t just a document viewer for some random format that I’m building. This is a browser for Gemini, a protocol and a format which are <em>very</em> intentionally designed to avoid the pitfalls and complexities of the web. But the most feature-complete way to build this is, because all the other available UI frameworks aren’t up to the (relatively simple) task, to pull in an entire web rendering engine. The very technology Gemini is trying to get away from. Isn’t that ironic.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I <a href="/2020/gemini-network-framework" data-link="/2020/gemini-network-framework">previously wrote</a> about building the networking stack using Network.framework. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Regardless of UI technology, narrating preformatted text with a screen reader is an interesting problem for the Gemini format. I can’t imagine listening to something naively read a block of code aloud would be pleasant. Let alone ASCII art, which is relatively common in Geminispace in lieu of inline images. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>Say goodbye to the days of extracting SF Mono from inside Terminal.app just to use it inside other text editors. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>More SwiftUI Text View Features</title>
      <link>https://shadowfacts.net/2020/more-swiftui-text-views/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/more-swiftui-text-views/</guid>
      <pubDate>Wed, 23 Sep 2020 21:35:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>In my <a href="/2020/swiftui-expanding-text-view/" data-link="/2020/swiftui-expanding-text-view/">last post</a>, I went over the implementation of a custom SwiftUI view that wraps <code>UITextView</code> in order to make it non-scrolling and auto-expanding. That worked quite well, but in continuing the reimplementation of Tusker’s compose screen in SwiftUI, I ran into a couple more things I had to re-implement myself.</p>
<!-- excerpt-end -->
<p>First up: a placeholder. Unfortunately, UIKit itself still does not provide a built-in way of adding placeholder text to <code>UITextView</code>, leaving it to app developers to implement themselves. When using UIKit, a simple solution is to add a <code>UILabel</code> on top of the text view and configuring it’s position and font to match the text view’s.</p>
<p>Replicating this is pretty simple, just move the wrapped <code>UITextView</code> inside a <code>ZStack</code> and add a <code>Text</code> for the placeholder. There are a couple slight complications, however. The first is that the text view’s content isn’t rendered starting at the very top left corner of the text view. Instead, it’s slightly inset. With the fonts matching, I found by trial-and-error that having the placeholder inset 4 points from the left and 8 points from the top aligned it to just about the correct position. There’s a slight discrepancy which seems to be a fraction of a point, but I decided to leave it at the nice, integral offsets. All but the most eagle-eyed users would never notice such a difference, especially as the placeholder disappears immediately when the user starts typing.</p>
<p>One additional caveat is that, unlike when you put a <code>UILabel</code> in front of a <code>UITextView</code>, when using a <code>ZStack</code> to position a <code>Text</code> on top of wrapped text view, the <code>Text</code> will intercept touches on the view behind it. So, if the text were empty and the user tapped on the placeholder label, the text view would not activate. A simple solution for this is to put the placeholder text behind the text view, which has the same visual appearance but results in the text view receiving all the touches within it.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ExpandingTextView: <span class="hl-type">View</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		<span class="hl-type">ZStack</span> {
			<span class="hl-kw">if</span> !text.<span class="hl-prop">isEmpty</span> {
				<span class="hl-type">Text</span>(<span class="hl-str">"Type something..."</span>)
					.<span class="hl-fn">font</span>(.<span class="hl-fn">system</span>(size: <span class="hl-num">20</span>))
					.<span class="hl-fn">foregroundColor</span>(.<span class="hl-prop">secondary</span>)
					.<span class="hl-fn">offset</span>(x: <span class="hl-num">4</span>, y: <span class="hl-num">8</span>)
			}

			<span class="hl-type">WrappedTextView</span>(text: <span class="hl-prop">$text</span>, textDidChange: <span class="hl-kw">self</span>.<span class="hl-prop">textDidChange</span>)
				.<span class="hl-fn">frame</span>(height: height ?? minHeight)
		}
	}
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>This of course introduces yet another caveat in that setting a background color on the wrapped <code>UITextView</code> is no longer possible—since it would obscure the placeholder. So, if you want to add a background color, it needs to be added to the <code>ZStack</code> behind both the text view and the placeholder so that everything appears to be in the correct order.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ExpandingTextView: <span class="hl-type">View</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		<span class="hl-type">ZStack</span> {
			<span class="hl-type">Color</span>(<span class="hl-type">UIColor</span>.<span class="hl-prop">secondarySystemBackground</span>)
			<span class="hl-cmt">// ...</span>
		}
	}
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>Finally, I needed to be able to programatically focus the text view so that the user can start typing immediately. Actually focusing the text view is done by calling the <code>becomeFirstResponder</code> method that is present on all <code>UIResponder</code> subclasses. This needs to be done from the <code>UIViewRepresentable</code> implementation, since it’s the only thing that has direct access to the underlying view. So, we need a mechanism to signal to the wrapper when it should instruct the text view to become the first responder. For this, the wrapped text view can take a binding to a boolean to indicate when the it should activate.</p>
<p>Whenever any of the inputs to the wrapped text view change (as well as immediately it’s first created), the <code>updateUIView</code> method is called to synchronize the wrapped <code>UIView</code>’s state with the SwiftUI wrapping struct’s state. In this method, if the flag is <code>true</code>, we can call the <code>becomeFirstResponder</code> method on the text view to activate it. The flag also needs to be set back to <code>false</code> so that if it’s changed later, it triggers another SwiftUI view update.</p>
<p>This all needs to happen inside the <code>DispatchQueue.main.async</code> block for two reasons. First, updating the view state inside during the view update is undefined behavior according to SwiftUI and should be avoided. The second reason is that, while calling <code>becomeFirstResponder</code> during <code>updateUIView</code> works fine on iOS 14, when running on iOS 13, it causes a crash deep inside UIKit, seemingly because the system is trying to present the keyboard before the view hierarchy is entirely on-screen.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> WrappedTextView: <span class="hl-type">UIViewRepresentable</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">@Binding var</span> becomeFirstResponder: <span class="hl-type">Bool</span>

	<span class="hl-kw">func</span> makeUIView(context: <span class="hl-type">Context</span>) -&gt; <span class="hl-type">UITextView</span> { <span class="hl-cmt">/* ... */</span> }

	<span class="hl-kw">func</span> updateUIView(<span class="hl-kw">_</span> uiView: <span class="hl-type">UITextView</span>, context: <span class="hl-type">Context</span>) {
		uiView.<span class="hl-prop">text</span> = <span class="hl-kw">self</span>.<span class="hl-prop">text</span>
		<span class="hl-type">DispatchQueue</span>.<span class="hl-prop">main</span>.<span class="hl-fn">async</span> {
			<span class="hl-kw">self</span>.<span class="hl-fn">textDidChange</span>(uiView)

			<span class="hl-kw">if self</span>.<span class="hl-prop">becomeFirstResponder</span> {
				uiView.<span class="hl-fn">becomeFirstResponder</span>()
				<span class="hl-kw">self</span>.<span class="hl-prop">becomeFirstResponder</span> = <span class="hl-kw">false</span>
			}
		}
	}
}
</code></pre>
<p>Actually making the text view focus when it appears is now a simple matter of giving the wrapped text view a binding to a boolean that we set to <code>true</code> during an <code>onAppear</code> callback.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ExpandingTextView: <span class="hl-type">View</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">@State private var</span> becomeFirstResponder = <span class="hl-kw">false
	@State private var</span> hasFirstAppeared = <span class="hl-kw">false

	var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		<span class="hl-type">ZStack</span> {
			<span class="hl-kw">if</span> !text.<span class="hl-prop">isEmpty</span> {
				<span class="hl-type">Text</span>(<span class="hl-str">"Type something..."</span>)
					.<span class="hl-fn">font</span>(.<span class="hl-fn">system</span>(size: <span class="hl-num">20</span>))
					.<span class="hl-fn">foregroundColor</span>(.<span class="hl-prop">secondary</span>)
					.<span class="hl-fn">offset</span>(x: <span class="hl-num">4</span>, y: <span class="hl-num">8</span>)
			}

			<span class="hl-type">WrappedTextView</span>(text: <span class="hl-prop">$text</span>, textDidChange: <span class="hl-kw">self</span>.<span class="hl-prop">textDidChange</span>, becomeFirstResponder: <span class="hl-prop">$becomeFirstResponder</span>)
				.<span class="hl-fn">frame</span>(height: height ?? minHeight)
		}
		.<span class="hl-fn">onAppear</span> {
			<span class="hl-kw">if</span> !hasFirstAppeared {
				hasFirstAppeared = <span class="hl-kw">true</span>
				becomeFirstResponder = <span class="hl-kw">true</span>
			}
		}
	}
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>In this example, there’s also a <code>hasFirstAppeared</code> state variable, so that the text view is only activated automatically the first time the view is shown. If the users dismisses the keyboard, leaves the app, and then returns, the keyboard should stay dismissed. This behavior could also be easily extracted out of the <code>ExpandingTextView</code> and into a container view, by having it pass a boolean binding through to the wrapper.</p>
]]></content:encoded>
    </item>
    <item>
      <title>SwiftUI Auto-Expanding Text Views</title>
      <link>https://shadowfacts.net/2020/swiftui-expanding-text-view/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/swiftui-expanding-text-view/</guid>
      <pubDate>Sat, 29 Aug 2020 15:20:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I’m currently in the process of rewriting the Compose screen in Tusker to use SwiftUI. This has mostly been a smooth process, but there have been a few hiccups, the first of which was the main text box. The updates to SwiftUI introduced in iOS 14 included <a href="https://developer.apple.com/documentation/swiftui/texteditor" data-link="developer.apple.com/documentation/swiftu…"><code>TextEditor</code></a>, the SwiftUI equivalent of <code>UITextView</code> to allow multi-line text editing. Unfortunately, there’s no (straightforward) way of disabling scrolling, making it unsuitable for some designs where the text view is embedded in a separate scroll view. Additionally, the fact that it’s not available at all on iOS 13 means layouts that require non-scrolling multi-line text editors must wrap <code>UITextView</code> themselves in order to be achievable with SwiftUI.</p>
<!-- excerpt-end -->
<p>You’d think this would be pretty simple: just use <code>UIViewRepresentable</code> with <code>UITextView</code> and disable scrolling. But if you try that approach, you’ll find a few issues that make things a bit more complex. While setting the <code>isScrollEnabled</code> property on the text view to <code>false</code> does indeed disable scrolling and make the text view expand itself as more lines are typed, the text view does not entirely respect SwiftUI’s layout system. Typing text that is larger than the available space causes the text view to expand outwards from the centerpoint, screwing up the layout, instead of wrapping the text onto the next line.</p>
<div>
	<video controls style="width: 50%; margin: 0 auto; display: block;" title="Screen recording of a non-scrolling UITextView inside SwiftUI being edited. When the text view grows, it expands from the center point and ends up offscreen.">
		<source src="/2020/swiftui-expanding-text-view/scroll-disabled.mp4" type="video/mp4">
	</video>
</div>
<p>Enabling scrolling on the text view partially solves this, making the text wrap whenever the user types something longer than fits on a single line. Of course, this also reintroduces the problem that the text view now scrolls instead of expanding to fit the contents. The simplest solution I’ve come up with for this problem is to have the SwiftUI side of the layout automatically expand the text view’s frame whenever the contents changes. So, even though the <code>UITextView</code> is allowed to scroll, it never will because the layout will ensure that the actual size of the view is always taller than the text’s height. Additionally, with bouncing disabled, there’s no indication from the user’s perspective that this is anything other than a regular non-scrolling text view.</p>
<p>Actually implementing this is pretty simple. There’s a <code>UIViewRepresentable</code> which wraps the <code>UITextView</code> and plumbs a <code>Binding&lt;String&gt;</code> up to the text view. It also stores a closure that receives the <code>UITextView</code> which is invoked whenever the text changes, using the <code>UITextViewDelegate</code> method. This will allow the actual SwiftUI view to listen for text view changes and update the frame of the wrapped view.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">import</span> SwiftUI

<span class="hl-kw">struct</span> WrappedTextView: <span class="hl-type">UIViewRepresentable</span> {
	<span class="hl-kw">typealias</span> UIViewType = <span class="hl-type">UITextView</span>

	<span class="hl-kw">@Binding var</span> text: <span class="hl-type">String</span>
	<span class="hl-kw">let</span> textDidChange: (<span class="hl-type">UITextView</span>) -&gt; <span class="hl-type">Void</span>

	<span class="hl-kw">func</span> makeUIView(context: <span class="hl-type">Context</span>) -&gt; <span class="hl-type">UITextView</span> {
		<span class="hl-kw">let</span> view = <span class="hl-type">UITextView</span>()
		view.<span class="hl-prop">isEditable</span> = <span class="hl-kw">true</span>
		view.<span class="hl-prop">delegate</span> = context.<span class="hl-prop">coordinator</span>
		<span class="hl-kw">return</span> view
	}

	<span class="hl-kw">func</span> updateUIView(<span class="hl-kw">_</span> uiView: <span class="hl-type">UITextView</span>, context: <span class="hl-type">Context</span>) {
		uiView.<span class="hl-prop">text</span> = <span class="hl-kw">self</span>.<span class="hl-prop">text</span>
		<span class="hl-type">DispatchQueue</span>.<span class="hl-prop">main</span>.<span class="hl-fn">async</span> {
			<span class="hl-kw">self</span>.<span class="hl-fn">textDidChange</span>(uiView)
		}
	}

	<span class="hl-kw">func</span> makeCoordinator() -&gt; <span class="hl-type">Coordinator</span> {
		<span class="hl-kw">return</span> <span class="hl-type">Coordinator</span>(text: <span class="hl-prop">$text</span>, textDidChange: textDidChange)
	}

	<span class="hl-kw">class</span> Coordinator: <span class="hl-type">NSObject</span>, <span class="hl-type">UITextViewDelegate</span> {
		<span class="hl-kw">@Binding var</span> text: <span class="hl-type">String</span>
		<span class="hl-kw">let</span> textDidChange: (<span class="hl-type">UITextView</span>) -&gt; <span class="hl-type">Void</span>

		<span class="hl-kw">init</span>(text: <span class="hl-type">Binding</span>&lt;<span class="hl-type">String</span>&gt;, textDidChange: <span class="hl-kw">@escaping</span> (<span class="hl-type">UITextView</span>) -&gt; <span class="hl-type">Void</span>) {
			<span class="hl-kw">self</span>.<span class="hl-prop">_text</span> = text
			<span class="hl-kw">self</span>.<span class="hl-prop">textDidChange</span> = textDidChange
		}

		<span class="hl-kw">func</span> textViewDidChange(<span class="hl-kw">_</span> textView: <span class="hl-type">UITextView</span>) {
			<span class="hl-kw">self</span>.<span class="hl-prop">text</span> = textView.<span class="hl-prop">text</span>
			<span class="hl-kw">self</span>.<span class="hl-fn">textDidChange</span>(textView)
		}
	}
}
</code></pre>
<p>One key line to note is that, in the <code>updateUIView</code> method, after the text is updated, the <code>textDidChange</code> closure is called. This is necessary because the <code>UITextView.text</code> setter does not call the delegate method automatically. So, if the text was changed programatically, the delegate method wouldn’t be called and, in turn, the did-change callback wouldn’t execute, preventing the height from updating. <code>DispatchQueue.main.async</code> is used to defer running updating the view height until the next runloop iteration for two reasons:</p>
<ol>
<li>So that we’re not modifying view state during view updates, as that’s undefined behavior in SwiftUI.</li>
<li>Because the UITextView doesn’t recalculate its content size immediately when the text is set.</li>
</ol>
<p>Waiting until the next runloop iteration solves both of those issues: the SwiftUI view updates will have finished and the text view will have recalculated its size.</p>
<p>The wrapping SwiftUI view is pretty simple. It passes the string binding through to the wrapped view and it also stores its minimum height, as well as an internal <code>@State</code> variable for the current height of the text view. The text view height is an optional, because before the text view appears, there is no height.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> ExpandingTextView: <span class="hl-type">View</span> {
	<span class="hl-kw">@Binding var</span> text: <span class="hl-type">String</span>
	<span class="hl-kw">let</span> minHeight: <span class="hl-type">CGFloat</span> = <span class="hl-num">150</span>
	<span class="hl-kw">@State private var</span> height: <span class="hl-type">CGFloat</span>?

	<span class="hl-kw">var</span> body: <span class="hl-kw">some</span> <span class="hl-type">View</span> {
		<span class="hl-type">WrappedTextView</span>(text: <span class="hl-prop">$text</span>, textDidChange: <span class="hl-kw">self</span>.<span class="hl-prop">textDidChange</span>)
			.<span class="hl-fn">frame</span>(height: height ?? minHeight)
	}

	<span class="hl-kw">private func</span> textDidChange(<span class="hl-kw">_</span> textView: <span class="hl-type">UITextView</span>) {
		<span class="hl-kw">self</span>.<span class="hl-prop">height</span> = <span class="hl-fn">max</span>(textView.<span class="hl-prop">contentSize</span>.<span class="hl-prop">height</span>, minHeight)
	}
}
</code></pre>
<p>Now, everything works correctly. The text view wraps text and expands to fit user input as expected, as well as updating its height when the content is altered in code.</p>
<div>
	<video controls style="width: 50%; margin: 0 auto; display: block;" title="Screen recording of a custom text view inside SwiftUI. When the text changes, the scroll view does not overflow and the height expands to fit the content.">
		<source src="/2020/swiftui-expanding-text-view/custom-wrapper.mp4" type="video/mp4">
	</video>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Implement a Gemini Protocol Client Using Network.framework</title>
      <link>https://shadowfacts.net/2020/gemini-network-framework/</link>
      <category>swift</category>
      <category>gemini</category>
      <guid>https://shadowfacts.net/2020/gemini-network-framework/</guid>
      <pubDate>Thu, 23 Jul 2020 01:57:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><a href="https://gemini.circumlunar.space/" data-link="gemini.circumlunar.space">Gemini</a> is a small protocol bulit on top of TCP and TLS that’s designed to serve as a transport mechanism primarily for text documents while lacking a great deal of the complexity of HTTP. <a href="https://developer.apple.com/documentation/network" data-link="developer.apple.com/documentation/networ…">Network.framework</a> was introduced to Apple’s platforms in 2018 as a modern framework for dealing with network connections and building custom network protocols. So, let’s use it to build a Gemini client implementation.</p>
<!-- excerpt-end -->
<h2 id="the-protocol"><a href="#the-protocol" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Protocol</h2>
<p>First, an overview of the Gemini protocol. This is going to be fairly brief, as there are some more details that I’m not going to go into, since this post is meant to focus on using Network.framework to build a TCP-based protocol client, rather than the protocol itself<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. If you’re interested, you can read more about the details of the protocol in its <a href="https://gemini.circumlunar.space/docs/specification.html" data-link="gemini.circumlunar.space/docs/specificat…">specification</a>.</p>
<p>At the highest level, Gemini is fairly similar to HTTP: every connection is made to request a single resource at a specific URL. After the connection is opened, and the TLS handshake completed, the client sends the request. The request is the CRLF-terminated absolute URL of the resource being requested. The URL string is encoded as UTF-8 and has a maximum length of 1024 bytes. The URL scheme doesn’t have to be specified, the default is <code>gemini://</code> when using the Gemini protocol for transport. The port is also optional, and defaults to <code>1965</code><sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>.</p>
<pre class="highlight" data-lang="plaintext"><code>gemini://example.com:1965/some/resource?foo&lt;CR&gt;&lt;LF&gt;
</code></pre>
<p>Likewise, the response starts with a CRLF-terminated, UTF-8 encoded string. It begins with a two digit status code, where the most significant digit defines the overall response type and the least significant digit provides more specificity. The status code is followed by a space character, then a string up to 1024 bytes in length, and finally the carriage return and line feed characters. The meaning of the meta string in the response is defined by the various status codes (for example, <code>20</code> is the status code for success and defines the meta string to be the MIME type of the response body).</p>
<pre class="highlight" data-lang="plaintext"><code>20 text/gemini&lt;CR&gt;&lt;LF&gt;
</code></pre>
<p>Finally, if the response was successful (i.e. the server returned status code in the <code>2x</code> range), there may be a response body, which is arbitrary binary data.</p>
<h2 id="the-implementation"><a href="#the-implementation" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Implementation</h2>
<p>With Network.framework, everything starts with an <code>NWProtocol</code>. The framework provides a bunch of concrete subclasses for dealing with protocols like TCP, UDP, and TLS. New in 2019 is the <code>NWProtocolFramer</code> class which provides an interface for defining your own protocols on top of the builtin stack. Using it starts with an class that conforms to the <code>NWProtocolFramerImplementation</code> protocol:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">import</span> Network

<span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-kw">static let</span> label = <span class="hl-str">"Gemini"</span>

	<span class="hl-kw">required init</span>(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) {}
}
</code></pre>
<p>The protocol has a bunch of requirements that need to be satisfied. Starting off with the simple ones, it needs a static read-only String variable called label, which will be used in log messages to identify which framer implementation is being used. It also needs an initializer which takes an <code>NWProtocolFramer.Instance</code>. Nothing needs to be done in this initializer—the framer instance doesn’t even need to be stored, since all of the other methods that have to be implemented directly receive it.</p>
<p>There’s also a static <code>definition</code> property which stores the <code>NWProtocolDefinition</code> that’s configured to use this class as the framer’s implementation. This needs to be a singleton, not constructed for every request, because it will later be used as a key to get some implementation-specific data out of other framework classes.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-kw">static let</span> definition = <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Definition</span>(implementation: <span class="hl-type">GeminiProtocol</span>.<span class="hl-kw">self</span>)
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>Next, there are a few other simple methods to implement:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">func</span> start(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">StartResult</span> {
		<span class="hl-kw">return</span> .<span class="hl-prop">ready</span>
	}

	<span class="hl-kw">func</span> wakeup(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) {
	}

	<span class="hl-kw">func</span> stop(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Bool</span> {
		<span class="hl-kw">return true</span>
	}

	<span class="hl-kw">func</span> cleanup(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) {
	}
}
</code></pre>
<p>Since the Gemini protocol doesn’t use long-running/persistent connections, there’s no work that needs to be done to start, wakeup, stop, or cleanup an individual connection. And, since each connection only handles a single request, there isn’t even any handshake that needs to be performed to start a Gemini connection. We can just send the request and we’re off to the races. Similarly, stopping a Gemini connection doesn’t mean anything, the connection is just closed.</p>
<p>Actually sending a request is nice and simple. The <code>NWProtocolFramerImplementation</code> protocol has a <code>handleOutput</code> method (output, in this case, meaning output <em>from</em> the client, i.e., the request). This method receives an instance of the protocol’s message type, which in this case is <code>NWProtocolFramer.Message</code>. Since <code>NWProtocolFramer</code> is designed to be used to implement application-level protocols, its message type functions as a key-value store that can contain arbitrary application protocol information.</p>
<p>For the Gemini protocol, a simple struct encapsulates all the data we need to make a request. All it does is ensure that the URL is no longer than 1024 bytes upon initialization (a limit defined by the protocol spec) and define a small helper property that creates a <code>Data</code> object containg the URL string encoded as UTF-8 with the carriage return and line feed characters appended.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> GeminiRequest {
	<span class="hl-kw">let</span> url: <span class="hl-type">URL</span>

	<span class="hl-kw">init</span>(url: <span class="hl-type">URL</span>) <span class="hl-kw">throws</span> {
		<span class="hl-kw">guard</span> url.<span class="hl-prop">absoluteString</span>.<span class="hl-prop">utf8</span>.<span class="hl-prop">count</span> &lt;= <span class="hl-num">1024</span> <span class="hl-kw">else</span> { <span class="hl-kw">throw</span> <span class="hl-type">Error</span>.<span class="hl-prop">urlTooLong</span> }
		<span class="hl-kw">self</span>.<span class="hl-prop">url</span> = url
	}

	<span class="hl-kw">var</span> data: <span class="hl-type">Data</span> {
		<span class="hl-kw">var</span> data = url.<span class="hl-prop">absoluteString</span>.<span class="hl-fn">data</span>(using: .<span class="hl-prop">utf8</span>)!
		data.<span class="hl-fn">append</span>(contentsOf: [<span class="hl-num">13</span>, <span class="hl-num">10</span>]) <span class="hl-cmt">// &lt;CR&gt;&lt;LF&gt;</span>
		<span class="hl-kw">return</span> data
	}

	<span class="hl-kw">enum</span> Error: <span class="hl-type">Swift</span>.<span class="hl-type">Error</span> {
		<span class="hl-kw">case</span> urlTooLong
	}
}
</code></pre>
<p>Also, a simple extension on <code>NWProtocolFramer.Message</code> provides access to the stored <code>GeminiRequest</code>, instead of dealing with string keys directly. There’s also a convenience initializer to create a message instance from a request that’s set up to use the protocol definition from earlier.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private let</span> requestKey = <span class="hl-str">"gemini_request"</span>

<span class="hl-kw">extension</span> <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Message</span> {
	<span class="hl-kw">convenience init</span>(geminiRequest request: <span class="hl-type">GeminiRequest</span>) {
		<span class="hl-kw">self</span>.<span class="hl-kw">init</span>(definition: <span class="hl-type">GeminiProtocol</span>.<span class="hl-prop">definition</span>)
		<span class="hl-kw">self</span>[requestKey] = request
	}

	<span class="hl-kw">var</span> geminiRequest: <span class="hl-type">GeminiRequest</span>? {
		<span class="hl-kw">self</span>[requestKey] <span class="hl-kw">as</span>? <span class="hl-type">GeminiRequest</span>
	}
}
</code></pre>
<p>With those both in place, the protocol implementation can simply grab the request out of the message and send its data through to the framer instance:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">func</span> handleOutput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>, message: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Message</span>, messageLength: <span class="hl-type">Int</span>, isComplete: <span class="hl-type">Bool</span>) {
		<span class="hl-kw">guard let</span> request = message.<span class="hl-prop">geminiRequest</span> <span class="hl-kw">else</span> {
			<span class="hl-fn">fatalError</span>(<span class="hl-str">"GeminiProtocol can't send message that doesn't have an associated GeminiRequest"</span>)
		}
		framer.<span class="hl-fn">writeOutput</span>(data: request.<span class="hl-prop">data</span>)
	}
}
</code></pre>
<p>Parsing input (i.e., the response from the server) is somewhat more complicated. Parsing the status code and the meta string will both follow a similar pattern. The <code>parseInput</code> method of <code>NWProtocolFramer.Instance</code> is used to get some input from the connection, given a valid range of lengths for the input. This method also takes a closure, which receives an optional <code>UnsafeMutableRawBufferPointer</code> containing the input data that was received as well as a boolean flag indicating if the connection has closed. It returns an integer representing the number of bytes that it consumed (meaning data that was fully parsed and should not be provided on subsequent <code>parseInput</code> calls). This closure is responsible for parsing the data, storing the result in a local variable, and returning how much, if any, of the data was consumed.</p>
<p>First off is the status code (and the following space character). In the protocol implementation, there’s a optional <code>Int</code> property used as temporary storage for the status code. If the <code>tempStatusCode</code> property is <code>nil</code>, the <code>parseInput</code> method is called on the framer. The length is always going to be 3 bytes (1 for each character of the status code, and 1 for the space). Inside the <code>parseInput</code> closure, if the buffer is not present or it’s not of the expected length, the closure returns zero to indicate that no bytes were consumed. Otherwise, the contents of the buffer are converted to a String and then parsed into an integer<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup> and stored in the temporary property (this is okay because the closure passed to <code>parseInput</code> is non-escaping, meaning it will be called before <code>parseInput</code> returns). Finally, the closure returns <code>3</code> to indicate that three bytes were consumed and should not be provided again as input.</p>
<p>Outside the <code>if</code>, there’s a <code>guard</code> that checks that there is a status code present, either from immediately prior or potentially from a previous invocation of the method. If not, it returns <code>3</code> from the <code>handleInput</code> method, telling the framework that that it expects there to be at least 3 bytes available before it’s called again. The reason the status code is stored in a class property, and why the code ensures that it’s <code>nil</code> before trying to parse, is so that if some subsequent parse step fails and the method returns and has to be invoked again in the future, it doesn’t try to re-parse the status code because the actual data for it has already been consumed.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private var</span> tempStatusCode: <span class="hl-type">Int</span>?

	<span class="hl-kw">func</span> handleInput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Int</span> {
		<span class="hl-kw">if</span> tempStatusCode == <span class="hl-kw">nil</span> {
			<span class="hl-kw">_</span> = framer.<span class="hl-fn">parseInput</span>(minimumIncompleteLength: <span class="hl-num">3</span>, maximumLength: <span class="hl-num">3</span>) { (buffer, isComplete) -&gt; <span class="hl-type">Int</span> <span class="hl-kw">in
				guard let</span> buffer = buffer, buffer.<span class="hl-prop">count</span> == <span class="hl-num">3</span> <span class="hl-kw">else</span> { <span class="hl-kw">return</span> <span class="hl-num">0</span> }
				<span class="hl-kw">let</span> secondIndex = buffer.<span class="hl-fn">index</span>(after: buffer.<span class="hl-prop">startIndex</span>)
				<span class="hl-kw">if let</span> str = <span class="hl-type">String</span>(bytes: buffer[...secondIndex], encoding: .<span class="hl-prop">utf8</span>),
				   <span class="hl-kw">let</span> value = <span class="hl-type">Int</span>(str, radix: <span class="hl-num">10</span>) {
					<span class="hl-kw">self</span>.<span class="hl-prop">tempStatusCode</span> = value
				}
				<span class="hl-kw">return</span> <span class="hl-num">3</span>
			}
		}
		<span class="hl-kw">guard let</span> statusCode = tempStatusCode <span class="hl-kw">else</span> {
			<span class="hl-kw">return</span> <span class="hl-num">3</span>
		}
	}
}
</code></pre>
<p>Next up: the meta string. Following the same pattern as with the status code, there’s a temporary property to store the result of parsing the meta string and a call to <code>parseInput</code>. This time, the minimum length is 2 bytes (since the Gemini spec doesn’t specify a minimum length for the meta string, it could be omitted entirely, which would leave just two bytes for the carriage return and line feed) and the maximum length is 1026 bytes (up to 1024 bytes for the meta string, and again, the trailing CRLF).</p>
<p>This time, the closure once again validates that there is enough data to at least attempt to parse it, but then it loops through the data looking for the CRLF sequence which defines the end of the meta string<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>. Afterwards, if the marker sequence was not found, the closure returns zero because no data was consumed. Otherwise, it constructs a string from the bytes up to the index of the carriage return, stores it in the temporary property, and returns the number of bytes consumed (<code>index</code> here represents the end index of the string, so without the additional <code>+ 2</code> the trailing CRLF would be considered part of the body). After the call to <code>parseInput</code>, it similarly checks that the meta was parsed successfully and returns if not.</p>
<p>One key difference between parsing the meta string and parsing the status code is that if the status code couldn’t be parsed, the exact number of bytes that must be available before it can be attempted again is always the same: 3. That’s not true when parsing the meta text: the number of bytes necessary for a retry is depedent on the number of bytes that were unsuccessfully attempted to be parsed. For that reason, there’s also an optional <code>Int</code> variable which stores the length of the buffer that the closure attempted to parse. When the closure executes, the variable is set to the length of the buffer. If, inside the closure, the code fails to find the carriage return and line feed characters anywhere, one of two things happens: If the buffer is shorter than 1026 bytes, the closure returns zero to indicate that nothing was consumed. Then, since there’s no string, the <code>handleInput</code> will return 1 plus the attempted meta length, indicating to the framework that it should wait until there is at least 1 additional byte of data available before calling <code>handleInput</code> again. If no CRLF was found, and the buffer count is greater than or equal to 1026, the closure simply aborts with a <code>fatalError</code> because the protocol specifies that the cannot be longer than 1024 bytes (it would be better to set some sort of ‘invalid’ flag on the response object and then pass that along to be handled by higher-level code, but for the purposes of this blog post, that’s not interesting code). In the final case, if parsing the meta failed and the <code>attemptedMetaLength</code> variable is <code>nil</code>, that means there wasn’t enough data available, so we simply return 2.</p>
<p><strong>Update July 7, 2021:</strong> The eagle-eyed among you may notice that there’s a flaw in the following implementation involving what happens when meta parsing has to be retried. I discovered this myself and discussed it in <a href="/2021/gemini-client-debugging/" data-link="/2021/gemini-client-debugging/">this follow-up post</a>.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private var</span> tempMeta: <span class="hl-type">String</span>?

	<span class="hl-kw">func</span> handleInput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Int</span> {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">var</span> attemptedMetaLength: <span class="hl-type">Int</span>?
		<span class="hl-kw">if</span> tempMeta == <span class="hl-kw">nil</span> {
			<span class="hl-kw">_</span> = framer.<span class="hl-fn">parseInput</span>(minimumIncompleteLength: <span class="hl-num">2</span>, maximumLength: <span class="hl-num">1026</span>) { (buffer, isComplete) -&gt; <span class="hl-type">Int</span> <span class="hl-kw">in
				guard let</span> buffer = buffer, buffer.<span class="hl-prop">count</span> &gt;= <span class="hl-num">2</span> <span class="hl-kw">else</span> { <span class="hl-kw">return</span> <span class="hl-num">0</span> }
				attemptedMetaLength = buffer.<span class="hl-prop">count</span>

				<span class="hl-kw">let</span> lastPossibleCRIndex = buffer.<span class="hl-fn">index</span>(before: buffer.<span class="hl-fn">index</span>(before: buffer.<span class="hl-prop">endIndex</span>))
				<span class="hl-kw">var</span> index = buffer.<span class="hl-prop">startIndex</span>
				<span class="hl-kw">var</span> found = <span class="hl-kw">false
				while</span> index &lt;= <span class="hl-fn">lastPossibleCRIndex</span> {
					<span class="hl-kw">if</span> buffer[index] == <span class="hl-num">13</span> <span class="hl-cmt">/* CR */</span> &amp;&amp; buffer[buffer.<span class="hl-fn">index</span>(after: index)] == <span class="hl-num">10</span> <span class="hl-cmt">/* LF */</span> {
						found = <span class="hl-kw">true
						break</span>
					}
					index = buffer.<span class="hl-fn">index</span>(after: index)
				}

				<span class="hl-kw">if</span> !found {
					<span class="hl-kw">if</span> buffer.<span class="hl-prop">count</span> &lt; <span class="hl-num">1026</span> {
						<span class="hl-kw">return</span> <span class="hl-num">0</span>
					} <span class="hl-kw">else</span> {
						<span class="hl-fn">fatalError</span>(<span class="hl-str">"Expected to find &lt;CR&gt;&lt;LF&gt; in buffer. Meta string may not be longer than 1024 bytes."</span>)
					}
				}

				tempMeta = <span class="hl-type">String</span>(bytes: buffer[..&lt;index], encoding: .<span class="hl-prop">utf8</span>)
				<span class="hl-kw">return</span> buffer.<span class="hl-prop">startIndex</span>.<span class="hl-fn">distance</span>(to: index) + <span class="hl-num">2</span>
			}
		}
		<span class="hl-kw">guard</span> didParseMeta, <span class="hl-kw">let</span> meta = tempMeta <span class="hl-kw">else</span> {
			<span class="hl-kw">if let</span> attempted = attemptedMetaLength {
				<span class="hl-kw">return</span> attempted + <span class="hl-num">1</span>
			} <span class="hl-kw">else</span> {
				<span class="hl-kw">return</span> <span class="hl-num">2</span>
			}
		}
	}
}
</code></pre>
<p>With the entire header parsed, an object can be constructed to represent the response metadata and an <code>NWProtocolFramer.Message</code> created to contain it.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">func</span> handleInput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Int</span> {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">let</span> header = <span class="hl-type">GeminiResponseHeader</span>(status: statusCode, meta: meta)
		<span class="hl-kw">let</span> message = <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Message</span>(geminiResponseHeader: header)
	}
}
</code></pre>
<p><code>GeminiResponseHeader</code> is a simple struct to contain the status code and the meta string in a type-safe manner:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> GeminiResponseHeader {
	<span class="hl-kw">let</span> status: <span class="hl-type">Int</span>
	<span class="hl-kw">let</span> meta: <span class="hl-type">String</span>
}
</code></pre>
<p>As with the request object, there’s a small extension on <code>NWProtocolFramer.Message</code> so that all the string keys are contained to a single place.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private let</span> responseHeaderKey = <span class="hl-str">"gemini_response_header"</span>

<span class="hl-kw">extension</span> <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Message</span> {
	<span class="hl-kw">convenience init</span>(geminiResponseHeader header: <span class="hl-type">GeminiResponseHeader</span>) {
		<span class="hl-kw">self</span>.<span class="hl-kw">init</span>(definition: <span class="hl-type">GeminiProtocol</span>.<span class="hl-prop">definition</span>)
		<span class="hl-kw">self</span>[responseHeaderKey] = header
	}

	<span class="hl-kw">var</span> geminiResponseHeader: <span class="hl-type">GeminiResponseHeader</span>? {
		<span class="hl-kw">self</span>[responseHeaderKey] <span class="hl-kw">as</span>? <span class="hl-type">GeminiResponseHeader</span>
	}
}
</code></pre>
<p>To actually pass the message off to the client of the protocol implementation, the <code>deliverInputNoCopy</code> method is used. Since the <code>handleInput</code> method has already parsed all of the data it needs to, and the response body is defined by the protocol to just be the rest of the response data, the <code>deliverInputNoCopy</code> method is a useful way of passing the data straight through to the protocol client, avoiding an extra memory copy. If the protocol had to transform the body of the response somehow, it could be read as above and then delivered to the protocol client with the <code>deliverInput(data:message:isComplete:)</code> method.</p>
<p>If the request was successful (i.e., the status code was in the 2x range), we try to receive as many bytes as possible, because the protocol doesn’t specify a way of determining the length of a response. All other response codes are defined to never have response bodies, so we don’t need to deliver any data. Using <code>.max</code> is a little bit weird, since we don’t actually <em>need</em> to receive that many bytes. But it seems to work perfectly fine in practice: once all the input is received and the other side closes the connection, the input is delivered without error.</p>
<p>Annoyingly, the return value of the Swift function is entirely undocumented (even in the generated headers, where the parameters are). Fortunately, the C equivalent (<code>nw_framer_deliver_input_no_copy</code>) is more thoroughly documented and provides an answer: the function returns a boolean indicating whether the input was delivered immediately or whether the framework will wait for more bytes before delivering it. We don’t care at all about this, so we just discard the return value.</p>
<p>Finally, we return 0 from <code>handleInput</code>. Ordinarily, this would mean that there must be zero or more bytes available before the framework calls us again. But, because we’ve delivered all the available input, that will never happen.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiProtocol: <span class="hl-type">NWProtocolFramerImplementation</span> {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">func</span> handleInput(framer: <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Instance</span>) -&gt; <span class="hl-type">Int</span> {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">_</span> = framer.<span class="hl-fn">deliverInputNoCopy</span>(length: statsCode.<span class="hl-prop">isSuccess</span> ? .<span class="hl-prop">max</span> : <span class="hl-num">0</span>, message: message, isComplete: <span class="hl-kw">true</span>)
		<span class="hl-kw">return</span> <span class="hl-num">0</span>
	}
}
</code></pre>
<p>Actually using the Gemini protocol implementation will require creating an <code>NWConnection</code> object, which takes an endpoint and connection parameters. The parameters define which protocols to use and the various options for them. The <code>NWParameters</code> class already defines a number of static <code>NWParameters</code> variables for commonly used protocols, so adding our own for Gemini fits right in.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">NWParameters</span> {
	<span class="hl-kw">static var</span> gemini: <span class="hl-type">NWParameters</span> {
		<span class="hl-kw">let</span> tcpOptions = <span class="hl-type">NWProtocolTCP</span>.<span class="hl-type">Options</span>()
		<span class="hl-kw">let</span> parameters = <span class="hl-type">NWParameters</span>(tls: geminiTLSOptions, tcp: tcpOptions)

		<span class="hl-kw">let</span> geminiOptions = <span class="hl-type">NWProtocolFramer</span>.<span class="hl-type">Options</span>(definition: <span class="hl-type">GeminiProtocol</span>.<span class="hl-prop">definition</span>)
		parameters.<span class="hl-prop">defaultProtocolStack</span>.<span class="hl-prop">applicationProtocols</span>.<span class="hl-fn">insert</span>(geminiOptions, at: <span class="hl-num">0</span>)

		<span class="hl-kw">return</span> parameters
	}
	<span class="hl-kw">private static var</span> geminiTLSOptions: <span class="hl-type">NWProtocolTLS</span>.<span class="hl-type">Options</span> {
		<span class="hl-kw">let</span> options = <span class="hl-type">NWProtocolTLS</span>.<span class="hl-type">Options</span>()
		<span class="hl-fn">sec_protocol_options_set_min_tls_protocol_version</span>(options.<span class="hl-prop">securityProtocolOptions</span>, .<span class="hl-prop">TLSv12</span>)
		<span class="hl-kw">return</span> options
	}
}
</code></pre>
<p>Here the only thing we customize about the TLS options is setting the minimum required version to TLS 1.2, as required by the Gemini spec. However, the Gemini spec further recommnds that clients implement a trust-on-first-use scheme to alllow people to host content on the Gemini network using self-signed certificates, but implementing that is out of the scope of this post. If you’re interested, a good starting point is the <code>sec_protocol_options_set_verify_block</code> function which lets you provide a closure that the framework uses to verify server certificates during the TLS handshake process.</p>
<p>Now, to make an API for all this that’s actually pleasant to use, I pretty closely followed the <code>URLSessionDataTask</code> approach from Foundation, since it models somthing fairly similar to Gemini.</p>
<p><code>GeminiDataTask</code> is a class which will store the request being sent, a completion handler, as well as an internal state and the underlying <code>NWConnection</code>. The initializer stores a few things, and then sets up the network connection. It uses the URL port, if it has one, otherwise the default of 1965. The host is simply the host of the requested URL. These are used to construct an <code>NWEndpoint</code> object and, combined with the Gemini <code>NWParameters</code> setup previously, create the connection. The convenience initializer also provides a slightly nicer API, so the user doesn’t have to directly deal with the <code>GeminiRequest</code> object (which, from their perspective, is useless since there’s nothing to customize about it beyond the plain old URL).</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-kw">typealias</span> Completion = (<span class="hl-type">Result</span>&lt;<span class="hl-type">GeminiResponse</span>, <span class="hl-type">Error</span>&gt;) -&gt; <span class="hl-type">Void</span>

	<span class="hl-kw">let</span> request: <span class="hl-type">GeminiRequest</span>
	<span class="hl-kw">private let</span> completion: <span class="hl-type">Completion</span>
	<span class="hl-kw">private(set) var</span> state: <span class="hl-type">State</span>
	<span class="hl-kw">private let</span> connection: <span class="hl-type">NWConnection</span>

	<span class="hl-kw">init</span>(request: <span class="hl-type">GeminiRequest</span>, completion: <span class="hl-kw">@escaping</span> <span class="hl-type">Completion</span>) {
		<span class="hl-kw">self</span>.<span class="hl-prop">request</span> = request
		<span class="hl-kw">self</span>.<span class="hl-prop">completion</span> = completion
		<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">unstarted</span>

		<span class="hl-kw">let</span> port = request.<span class="hl-prop">url</span>.<span class="hl-prop">port</span> != <span class="hl-kw">nil</span> ? <span class="hl-type">UInt16</span>(request.<span class="hl-prop">url</span>.<span class="hl-prop">port</span>!) : <span class="hl-num">1965</span>
		<span class="hl-kw">let</span> endpoint = <span class="hl-type">NWEndpoint</span>.<span class="hl-fn">hostPort</span>(host: <span class="hl-type">NWEndpoint</span>.<span class="hl-type">Host</span>(request.<span class="hl-prop">url</span>.<span class="hl-prop">host</span>!), port: <span class="hl-type">NWEndpoint</span>.<span class="hl-type">Port</span>(rawValue: port)!)
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span> = <span class="hl-type">NWConnection</span>(to: endpoint, using: .<span class="hl-prop">gemini</span>)
	}

	<span class="hl-kw">convenience init</span>(url: <span class="hl-type">URL</span>, completion: <span class="hl-kw">@escaping</span> <span class="hl-type">Completion</span>) <span class="hl-kw">throws</span> {
		<span class="hl-kw">self</span>.<span class="hl-kw">init</span>(request: <span class="hl-kw">try</span> <span class="hl-type">GeminiRequest</span>(url: url), completion: completion)
	}
}
</code></pre>
<p>The <code>State</code> enum is quite simple, just a few cases. It isn’t used for much, just keeping track of the internal state so that the task doesn’t try to perform any invalid operations on the connection.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">extension</span> <span class="hl-type">GeminiDataTask</span> {
	<span class="hl-kw">enum</span> State {
		<span class="hl-kw">case</span> unstarted, started, completed
	}
}
</code></pre>
<p>There’s also a small helper struct to combine the response body and metadata into a single object:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">struct</span> GeminiResponse {
	<span class="hl-kw">let</span> header: <span class="hl-type">GeminiResponseHeader</span>
	<span class="hl-kw">let</span> body: <span class="hl-type">Data</span>?

	<span class="hl-kw">var</span> status: <span class="hl-type">Int</span> { header.<span class="hl-prop">status</span> }
	<span class="hl-kw">var</span> meta: <span class="hl-type">String</span> { header.<span class="hl-prop">meta</span> }
}
</code></pre>
<p>There are also some small methods to start and stop the request. I also copied the behavior from <code>URLSessionTask</code> where the task is automatically cancelled when all references to it are released.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">deinit</span> {
		<span class="hl-kw">self</span>.<span class="hl-fn">cancel</span>()
	}

	<span class="hl-kw">func</span> resume() {
		<span class="hl-kw">guard self</span>.<span class="hl-prop">state</span> == .<span class="hl-prop">unstarted</span> <span class="hl-kw">else</span> { <span class="hl-kw">return</span> }
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">start</span>(queue: <span class="hl-type">GeminiDataTask</span>.<span class="hl-prop">queue</span>)
		<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">started</span>
	}

	<span class="hl-kw">func</span> cancel() {
		<span class="hl-kw">guard</span> state != .<span class="hl-prop">completed</span> <span class="hl-kw">else</span> { <span class="hl-kw">return</span> }
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">cancel</span>()
		<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">completed</span>
	}
}
</code></pre>
<p>When the connection starts, it needs to know which <code>DispatchQueue</code> to call its handler blocks on. For simplicity, here there’s just a single queue used for all Gemini tasks.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-kw">static let</span> queue = <span class="hl-type">DispatchQueue</span>(label: <span class="hl-str">"GeminiDataTask"</span>, qos: .<span class="hl-prop">default</span>)
	<span class="hl-cmt">// ...</span>
}
</code></pre>
<p>Also in the initializer, the <code>stateUpdateHandler</code> property of the connection is set to a closure which receives the connection’s new state. If the connection has become ready, it sends the request. If the connection has errored for some reason, it ensures that it’s closed and reports the error to the task’s completion handler.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">init</span>(request: <span class="hl-type">GeminiRequest</span>, completion: <span class="hl-kw">@escaping</span> <span class="hl-type">Completion</span>) {
		<span class="hl-cmt">// ...</span>
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-prop">stateUpdateHandler</span> = { (newState) <span class="hl-kw">in
			switch</span> newState {
			<span class="hl-kw">case</span> .<span class="hl-prop">ready</span>:
				<span class="hl-kw">self</span>.<span class="hl-fn">sendRequest</span>()
			<span class="hl-kw">case let</span> .<span class="hl-fn">failed</span>(error):
				<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">completed</span>
				<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">cancel</span>()
				<span class="hl-kw">self</span>.<span class="hl-fn">completion</span>(.<span class="hl-fn">failure</span>(error))
			<span class="hl-kw">default</span>:
				<span class="hl-kw">break</span>
			}
		}
	}
}
</code></pre>
<p>To actually send the request, an <code>NWProtocoFramer.Message</code> is constructed for the request using the convenience initializer added earlier. Then, a custom connection context is instantiated, using the message as its metadata. The message isn’t sent directly, so the connection context is how <code>NWProtocolFramer</code> will later get access to it. There’s no data sent because Gemini requests can’t have any body and the only data required is already encoded by the <code>GeminiRequest</code> object. Since the spec states that every connection corresponds to exactly one request, the request is completed immediately. The only thing the send completion handler needs to do is check if an error occurred while sending the request, and if so, cancel the connection and report the error.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> sendRequest() {
		<span class="hl-kw">let</span> message = <span class="hl-type">NWProtocoFramer</span>.<span class="hl-type">Message</span>(geminiRequest: <span class="hl-kw">self</span>.<span class="hl-prop">request</span>)
		<span class="hl-kw">let</span> context = <span class="hl-type">NWConnection</span>.<span class="hl-type">ContentContext</span>(identifier: <span class="hl-str">"GeminiRequest"</span>, metadata: [message])
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">send</span>(content: <span class="hl-kw">nil</span>, contentContext: context, isComplete: <span class="hl-kw">true</span>, completion: .<span class="hl-fn">contentProcessed</span>({ (<span class="hl-kw">_</span>) <span class="hl-kw">in
			if let</span> error = error {
				<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">completed</span>
				<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">cancel</span>()
				<span class="hl-kw">self</span>.<span class="hl-fn">completion</span>(.<span class="hl-fn">failure</span>(error))
			}
		}))
		<span class="hl-kw">self</span>.<span class="hl-fn">receive</span>()
	}
}
</code></pre>
<p>Once the request has been sent, the <code>receive</code> method is called on the task to setup the receive handler for the connection. The receive closure takes the data that was received, another content context, whether the request is completed, and any error that may have occurred. In all cases, it closes the connection and sets the task’s internal state to completed. If there was an error, it’s reported via the task’s completion handler. As when sending the request, the <code>NWConnection</code> has no direct knowledge of the <code>NWProtocolFramer</code> and its messages, so those have to pulled out via the context. If the message and header were found, then the header is bundled up with the rest of the data that was received into a response object which is given to the completion handler.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">class</span> GeminiDataTask {
	<span class="hl-cmt">// ...</span>
	<span class="hl-kw">private func</span> receive() {
		<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">receiveMessage</span> { (data, context, isComplete, error) <span class="hl-kw">in
			if let</span> error = error {
				<span class="hl-kw">self</span>.<span class="hl-fn">completion</span>(.<span class="hl-fn">failure</span>(error))
			} <span class="hl-kw">else if let</span> message = context?.<span class="hl-fn">protocolMetadata</span>(definition: <span class="hl-type">GeminiProtocol</span>.<span class="hl-prop">definition</span>) <span class="hl-kw">as</span>? <span class="hl-type">NWProtocoFramer</span>.<span class="hl-type">Message</span>,
			          <span class="hl-kw">let</span> header = message.<span class="hl-fn">geminiResponseHeader</span> {
				<span class="hl-kw">let</span> response = <span class="hl-type">GeminiResponse</span>(header: header, body: data)
				<span class="hl-kw">self</span>.<span class="hl-fn">completion</span>(.<span class="hl-fn">success</span>(response))
			}

			<span class="hl-kw">self</span>.<span class="hl-prop">connection</span>.<span class="hl-fn">cancel</span>()
			<span class="hl-kw">self</span>.<span class="hl-prop">state</span> = .<span class="hl-prop">completed</span>
		}
	}
}
</code></pre>
<p>To recap, here’s how it all fits together: First, the user constructs a <code>GeminiDataTask</code> representing the request. Next, to kickoff the request, the user calls the <code>resume</code> method on it. This starts the underlying <code>NWConnection</code> which establishes a TCP connection and performs the TLS handshake. Once the network connection is ready, its <code>stateUpdateHandler</code> closure is notified, causing the <code>sendRequest</code> method to be called on the task. That method then creates the actual message object, gives it to the connection to send, and then sets up a handler to be called when a response is received. Using the request message and the <code>GeminiProtocol</code> implementation, <code>Network.framework</code> gets the raw bytes to send over the network. The framework then waits in the background to receive a respone from the server. Once data is received from the server and has been decrypted, it returns to the <code>GeminiProtocol</code> which parses the metadata and then sends the rest of the data on to the protocol client. Upon receipt of the full metadata and message, the receive closure is called. The closure then passes the result of the request—either an error or the Gemini response—to the completion handler and closes the connection.</p>
<p>At the end of all this, the API we’ve got is a nice simple abstraction over a network protocol that should be fairly familiar to most Apple-platform developers:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> task = <span class="hl-type">GeminiDataTask</span>(url: <span class="hl-type">URL</span>(string: <span class="hl-str">"gemini://gemini.circumlunar.space/"</span>)!) { (result)
	<span class="hl-fn">print</span>(<span class="hl-str">"Status:</span> \(result.<span class="hl-prop">status</span>)<span class="hl-str">"</span>)
	<span class="hl-fn">print</span>(<span class="hl-str">"Meta: '</span>\(result.<span class="hl-prop">meta</span>)<span class="hl-str">'"</span>)
	<span class="hl-kw">if let</span> data = result.<span class="hl-prop">data</span>, <span class="hl-kw">let</span> str = <span class="hl-type">String</span>(data: data, encoding: .<span class="hl-prop">utf8</span>) {
		<span class="hl-fn">print</span>(str)
	}
}
task.<span class="hl-fn">resume</span>()
</code></pre>
<p>Network.framework is a super is useful tool for writing custom networking code and building abstractions on top of relatively low level protocols. The example I gave here isn’t a hypothetical, I’m using Network.framework and almost this exact code to build a <a href="https://git.shadowfacts.net/shadowfacts/Gemini" data-link="git.shadowfacts.net/shadowfacts/Gemini">Gemini browser</a> app for Mac and iOS.</p>
<p>This post has barely scratched the surface, there’s even more interesting stuff the framework is capable of, such as building peer-to-peer protocols. The documentation, in particular the <a href="https://developer.apple.com/documentation/network/building_a_custom_peer-to-peer_protocol" data-link="developer.apple.com/documentation/networ…">Tic-Tac-Toe sample project</a> is great resource for seeing more of what’s possible.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>That said, the rest of the Gemini protocol, as well as the text format, and the community that’s sprung up around it is super interesting, and you should definitely check it out. An easy way to start is by using a Gemini-to-web proxy. Checkout the <a href="https://proxy.vulpes.one/gemini/gemini.circumlunar.space" data-link="proxy.vulpes.one/gemini/gemini.circumlun…">homepage</a> and explore from there. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Because the first <a href="https://en.wikipedia.org/wiki/Gemini_3" data-link="en.wikipedia.org/wiki/Gemini_3">crewed mission</a> of the Gemini Program launched on March 23, 1965. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>If you were really building an implementation of the Gemini protocol, you would probably want to wrap the raw integer status code in something else to avoid dealing with magic numbers throughout your codebase. An enum backed by integer values, perhaps. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>You can’t scan through the data backwards, because the response body immediately follows the CRLF after the meta string, so you could end up finding a CRLF sequence inside the body and incorrectly basing the length of the meta string off that. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Replicating Safari&apos;s Link Preview Animation</title>
      <link>https://shadowfacts.net/2020/uipreviewparameters-textlinerects/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/uipreviewparameters-textlinerects/</guid>
      <pubDate>Fri, 03 Jul 2020 20:28:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><strong>Update:</strong> See <a href="/2022/textkit-2/" data-link="/2022/textkit-2/">the follow up</a> for info about adapting this to TextKit 2.</p>
<p>In iOS 13, Apple replaced the Peek and Pop force touch system with new context menus and previews<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. These new previews have a fancy animation for when the appear, in which they expand out of the content and onto the screen. Back when I first replaced the previews in Tusker with the new context menus (over a year ago, shortly after WWDC19), I wanted to replicate the behavior in Safari for links and mentions in post bodies. At the time, there was pretty much zero official documentation about the new context menu APIs, so I decided to wait for either Apple to publish docs or for someone else to figure it out first. Now that WWDC20 has come and gone, and I’ve been working on it a little bit at a time for over a year, I finally have a fully working implementation.</p>
<!-- excerpt-end -->
<p>Here’s what the Safari behavior looks like with animations slowed down, both with a single-line link and one that spans multiple lines:</p>
<div>
	<video controls style="width: 50%; float: left;" title="Screen recording of a single-line link being previewed in Safari on iOS">
		<source src="/2020/uipreviewparameters-textlinerects/safari.mp4" type="video/mp4">
	</video>
	<video controls style="width: 50%; float: right;" title="Screen recording of a multi-line link being previewed in Safari on iOS">
		<source src="/2020/uipreviewparameters-textlinerects/safari-multiline.mp4" type="video/mp4">
	</video>
</div>
They both look pretty much like you'd expect. In the single-line case, the text of the link appears inside a sort of bubble which animates into the link preview. For the multi-line link, it's pretty similar, but there are two bubbles each of which contains the part of the link that's on their respective lines. If the text fragments overlapped horizontally at all, then the two bubbles would be merged together. By default, if you don't provide a custom preview, UIKit will simply display the entire view during the animation. This doesn't look great, particularly for long pieces of text, of which the link may only be a small portion. Only highlighting the link text looks much better. So, let's see what it takes to reimplement the same behavior as Safari.
<p>First, a little bit about how custom previews work: From the <code>contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)</code> method, you provide a custom <code>UITargetedPreview</code> describing the configuration of the preview itself. It takes the view to be displayed as the preview, a preview parameters object, and a preview target object. The preview parameters describe how the preview should be displayed and the preview target defines what view the preview view should be within and where inside that view it should be anchored. So, in the <code>previewForHighlightingMenuWithConfiguration</code> method, we need to construct all of these and then assemble them into a targeted preview.</p>
<p>The most obvious starting place is the <a href="https://developer.apple.com/documentation/uikit/uipreviewparameters/3295952-init" data-link="developer.apple.com/documentation/uikit/…"><code>UIPreviewParameters(textLineRects:)</code></a> initializer, since it directly deals with the behavior we’re trying to replicate. It takes an array of <code>CGRect</code>s (wrapped in <code>NSValue</code>s) representing the rectangles occupied by the text (one rect per line the text is on) in the coordinate space of the preview view.</p>
<p>Because this is a <code>UITextView</code> subclass, the CoreText stack is already setup and we can directly access it. <code>NSLayoutManager</code> has a handy method called <code>enumerateEnclosingRects(forGlyphRange:withinSelectedGlyphRange:in:)</code> which takes the range of some text and gives us access to the rectangles that the text occupies.</p>
<p>But to use that method, we need to get the link range from somewhere. Since we have the context menu interaction in the <code>previewForHighlightingMenuWithConfiguration</code> method, we could ask it for its location within ourself and then find the link and range at that point, but that would be duplicating work done almost immediately before in the <code>contextMenuInteraction(_:configurationForMenuAtLocation:)</code> method. Instead, we’ll store the link range on a private property of the text view subclass from the <code>configurationForMenuAtLocation</code> method, and then retrieve it in <code>previewForHighlightingMenuWithConfiguration</code>. (The code below assumes that there already exists a method called <code>getLinkRangeAtPoint</code> which takes a point inside the text view and returns the range that the link spans, if there is one.)</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private var</span> currentPreviewedLinkRange: <span class="hl-type">NSRange</span>?

<span class="hl-kw">func</span> contextMenuInteraction(<span class="hl-kw">_</span> interaction: <span class="hl-type">UIContextMenuInteraction</span>, configurationForMenuAtLocation location: <span class="hl-type">CGPoint</span>) -&gt; <span class="hl-type">UIContextMenuConfiguration</span> {
  <span class="hl-kw">guard let</span> range = <span class="hl-kw">self</span>.<span class="hl-fn">getLinkRangeAtPoint</span>(location) <span class="hl-kw">else</span> {
      <span class="hl-kw">return nil</span>
  }
  <span class="hl-kw">self</span>.<span class="hl-prop">currentPreviewedLinkRange</span> = range
  <span class="hl-kw">return</span> <span class="hl-cmt">// ...</span>
}
</code></pre>
<p>Then, in the <code>contextMenuInteraction(_:previewForHighlightingMenuWithConfiguration:)</code> method, we can grab the stored range and get to creating the preview. Returning <code>nil</code> from this method will simply use the default preview configuration, showing the entire text view in the preview animation, which is a reasonable fallback if for some reason we don’t have the link range.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> contextMenuInteraction(<span class="hl-kw">_</span> interaction: <span class="hl-type">UIContextMenuInteraction</span>, previewForHighlightingMenuWithConfiguration configuration: <span class="hl-type">UIContextMenuConfiguration</span>) -&gt; <span class="hl-type">UITargetedPreview</span>? {
    <span class="hl-kw">guard let</span> linkRange = currentPreviewedLinkRange <span class="hl-kw">else</span> {
        <span class="hl-kw">return nil</span>
    }
    currentPreviewedLinkRange = <span class="hl-kw">nil</span>
}
</code></pre>
<p>With the link range, we can use the <code>enumerateEnclosingRects</code> method on the text view’s layout manager and then use those rectangles to construct the preview parameters object.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> notFoundRange = <span class="hl-type">NSRange</span>(location: <span class="hl-type">NSNotFound</span>, length: <span class="hl-num">0</span>)
<span class="hl-kw">var</span> textLineRects = [<span class="hl-type">CGRect</span>]()
<span class="hl-kw">self</span>.<span class="hl-prop">layoutManager</span>.<span class="hl-fn">enumerateEnclosingRects</span>(forGlyphRange: linkRange,
                                           withinSelectedGlyphRange: notFoundRange,
                                           in: <span class="hl-kw">self</span>.<span class="hl-prop">textContainer</span>) { (rect, stop) <span class="hl-kw">in</span>
    textLineRects.<span class="hl-fn">append</span>(rect)
}

<span class="hl-kw">let</span> parameters = <span class="hl-type">UIPreviewParameters</span>(textLineRects: textLineRects <span class="hl-kw">as</span> [<span class="hl-type">NSValue</span>])
</code></pre>
<p>Now that we’ve finally got the text line rects and the preview parameters, we can move on to the next piece of the puzzle: the view that’s going to be shown in the preview animation. You might think that we could use <code>self</code> as the preview view, but that wouldn’t work. While the animation is running, the preview view is removed from the regular view hierarchy, meaning the rest of the text would disappear while the animation is running (what’s more, since later we’ll use <code>self</code> as the target for the preview, the preview itself wouldn’t even appear). We could try to duplicate ourself, and copy over all the layout-impacting attributes, but that’s just asking for slight layout differences.<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup> Instead, to ensure we get a view that appears exactly the same as our text view, we can use a <a href="https://developer.apple.com/documentation/uikit/uiview/1622531-snapshotview" data-link="developer.apple.com/documentation/uikit/…">snapshot view</a>.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">guard let</span> snapshot = <span class="hl-kw">self</span>.<span class="hl-fn">snapshotView</span>(afterScreenUpdates: <span class="hl-kw">false</span>) <span class="hl-kw">else</span> {
  	<span class="hl-kw">return nil</span>
}
</code></pre>
<p>Next, we need to create a preview target. Reading the documentation, you might notice the <code>UITargetedPreview(view:parameters:)</code> initializer and wonder why this is even necessary. Well, if you try to use that initializer with a snapshot view, your app will crash because the snapshot view hasn’t been added to the view hierarchy, and therefore, because there’s no target, UIKit doesn’t know where to put it. The <code>UIPreviewTarget</code> describes exactly that. It needs to know the container view that the preview will placed in (simply <code>self</code>, since we’re in the text view) as well as where inside the target container the center of the preview should be anchored. We want to anchor the center point of the preview view such that the text of the link appears to be in the exact same place. With the text line rects, we can determine the overall bounds of the link’s text fragment. From there, since the preview will have the same bounding rectangle as the link text, we can just use the center of the rect enclosing the text.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> minX = <span class="hl-type">CGFloat</span>.<span class="hl-prop">greatestFiniteMagnitude</span>, maxX = -<span class="hl-type">CGFloat</span>.<span class="hl-prop">greatestFiniteMagnitude</span>,
    minY = <span class="hl-type">CGFloat</span>.<span class="hl-prop">greatestFiniteMagnitude</span>, maxY = -<span class="hl-type">CGFloat</span>.<span class="hl-prop">greatestFiniteMagnitude</span>
<span class="hl-kw">for</span> rect <span class="hl-kw">in</span> textLineRects {
    minX = <span class="hl-fn">min</span>(rect.<span class="hl-prop">minX</span>, minX)
    maxX = <span class="hl-fn">max</span>(rect.<span class="hl-prop">maxX</span>, maxX)
    minY = <span class="hl-fn">min</span>(rect.<span class="hl-prop">minY</span>, minY)
    maxY = <span class="hl-fn">max</span>(rect.<span class="hl-prop">maxY</span>, maxY)
}
<span class="hl-kw">let</span> textLineRectsCenter = <span class="hl-type">CGPoint</span>(x: (minX + maxX) / <span class="hl-num">2</span>, y: (minX + maxX) / <span class="hl-num">2</span>)
<span class="hl-kw">let</span> target = <span class="hl-type">UIPreviewTarget</span>(container: <span class="hl-kw">self</span>, center: textLineRectsCenter)
</code></pre>
<p>Then, we can finally construct and return the targeted preview:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">return</span> <span class="hl-type">UITargetedPreview</span>(view: snapshot, parameters: parameters, target: target)
</code></pre>
<p>If we run this, the preview animation will be limited to the text of the link, and it looks pretty good:</p>
<div>
	<video controls style="width: 50%; float: left;" title="Screen recording of a link being previewed appearing as expected.">
		<source src="/2020/uipreviewparameters-textlinerects/unmasked.mp4" type="video/mp4">
	</video>
	<video controls style="width: 50%; float: right;" title="Screen recording of a link being previewed next to other text, with the other text visible inside the preview animation.">
		<source src="/2020/uipreviewparameters-textlinerects/unmasked-broken.mp4" type="video/mp4">
	</video>
</div>
<p>Unfortunately, there’s still a pretty big problem: if the link is near enough other text, and particularly if it spans multiple lines, the text that’s visually near the link will be partly visible in the preview. This happens because UIKit takes the text line rects passed into <code>UIPreviewParameters</code>, does some stuff to expand them and round the corners and merge them, creating the bubble shape, and then masks the preview view to the resulting path. Unfortunately, it doesn’t mask the text beforehand; it masks everything in one pass. So, what we need to do ourselves before giving UIKit the preview view is mask to directly around the text, preventing anything else from seeping in.</p>
<p>To do this, we have to do something similar to what UIKit is doing. We need to generate a path which contains the entirety of all of the text line rects, and nothing more. (Note: this is not the convex hull problem, since we don’t need <em>a</em> path that contains the points of all the rectangles, we need the <em>smallest</em> path that encloses them.) Implementing this isn’t particularly interesting, and is left as an exercise to the reader<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>. Assuming there’s a custom initializer <code>UIBezierPath(wrappingAroundRects:)</code> which produces a path from an array of <code>CGRect</code>s, the obvious thing to do is mask the view to that path using a layer mask with a <code>CAShapeLayer</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> path = <span class="hl-type">UIBezierPath</span>(wrappingAroundRects: textLineRects)
<span class="hl-kw">let</span> maskLayer = <span class="hl-type">CAShapeLayer</span>()
maskLayer.<span class="hl-prop">path</span> = path.<span class="hl-prop">cgPath</span>
snapshot.<span class="hl-prop">layer</span>.<span class="hl-prop">mask</span> = maskLayer
</code></pre>
<p>Running this, however, doesn’t quite work. Everything looks exactly the same as before, with the nearby text appearing inside the preview during the animation. You might check the documentation and think to try the <code>visiblePath</code> attribute on <code>UIPreviewParameters</code>. Unfortunately, that entirely overrides the mask generated by the <code>textLineRects</code> initializer, the exact opposite of the current problem.</p>
<p>It seems that, when using the <code>UIPreviewParameters(textLineRects:)</code> initializer, UIKit will silently discard any existing layer mask on the view provided as the preview view (FB7832297). This is also true for <a href="https://developer.apple.com/documentation/uikit/uiview/1622557-mask" data-link="developer.apple.com/documentation/uikit/…"><code>UIView</code> masks</a>. This caused a great deal of hair-pulling for me, until I disabled the preview parameters stuff and the mask suddenly started working. The simple workaround for this is to just apply the mask to the snapshot view, embed the snapshot inside an additional container view of the same size, and then use that container as the view for the <code>UITargetedPreview</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> snapshotContainer = <span class="hl-type">UIView</span>(frame: snapshot.<span class="hl-prop">bounds</span>)
snapshotContainer.<span class="hl-fn">addSubview</span>(snapshot)
<span class="hl-kw">return</span> <span class="hl-type">UITargetedPreview</span>(view: snapshotContainer, parameters: parameters, target: target)
</code></pre>
<p>And with that, only the link text is visible in the preview animation and it expands nicely into the full preview:</p>
<div>
	<video controls style="width: 50%; margin: 0 auto; display: block;" title="Screen recording of a link being previewed and dismissed with the link text animating back to its starting position upon dismissal.">
		<source src="/2020/uipreviewparameters-textlinerects/masked.mp4" type="video/mp4">
	</video>
</div>
<p>But, there’s still one small detail as keen-eyed readers may have noticed. In Safari, when dismissing the full preview, it animates back into the preview view and springs back to the original position. With our implementation, however, it doesn’t. The preview view controller does animate back into the preview view, however, instead of returning to the original position, it disappears off into the middle of the screen. This is because there’s still one <code>UIContextMenuInteractionDelegate</code> method we need to implement: <code>contextMenuInteraction(_:previewForDismissingMenuWithConfiguration:)</code>. Similar to the <code>previewForHighlighting</code> method, this method takes the interaction and the context menu configuration, creating a <code>UITargetedPreview</code> that should be used during the dismissal animation. Since we want the preview to go back to the same location while dismissing as it came from while expanding, we can cache the targeted preview we’ve already constructed for the highlight method and return it from the dismissal method.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">private weak var</span> activeTargetedPreview: <span class="hl-type">UITargetedPreview</span>?

<span class="hl-kw">func</span> contextMenuInteraction(<span class="hl-kw">_</span> interaction: <span class="hl-type">UIContextMenuInteraction</span>, previewForHighlightingMenuWithConfiguration configuration: <span class="hl-type">UIContextMenuConfiguration</span>) -&gt; <span class="hl-type">UITargetedPreview</span>? {
    <span class="hl-cmt">// ...</span>
    <span class="hl-kw">let</span> preview = <span class="hl-type">UITargetedPreview</span>(view: snapshotContainer, parameters: parameters, target: target)
    <span class="hl-kw">self</span>.<span class="hl-prop">activeTargetedPreview</span> = preview
    <span class="hl-kw">return</span> preview
}

<span class="hl-kw">func</span> contextMenuInteraction(<span class="hl-kw">_</span> interaction: <span class="hl-type">UIContextMenuInteraction</span>, previewForDismissingMenuWithConfiguration configuration: <span class="hl-type">UIContextMenuConfiguration</span>) -&gt; <span class="hl-type">UITargetedPreview</span>? {
    <span class="hl-kw">return self</span>.<span class="hl-prop">activeTargetedPreview</span>
}
</code></pre>
<p>Now, when dismissing the preview, it animates back into the link text where it originally came from:</p>
<div>
  <video controls style="width: 50%; margin: 0 auto; display: block;" title="Screen recording of a link being previewed and dismissed with the link text animating back to its starting position upon dismissal.">
  	<source src="/2020/uipreviewparameters-textlinerects/dismiss.mp4" type="video/mp4">
  </video>
</div>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I still miss popping into view controllers. RIP. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>Indeed, when I attempted exactly this, there was some attribute I couldn’t find (even through diffing the internal descriptions of each text view) that was altering the layout of the preview copy. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>Or, if you really wanted, you could look at my fairly primitive <a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/commit/f86d3a0ed15ac23a77c47d9f56deb91e2eba661c/Tusker/Extensions/UIBezierPath+Helpers.swift" data-link="git.shadowfacts.net/shadowfacts/Tusker/s…">solution</a>. In a nutshell: it constructs a path by starting at the top left corner of the top-most rect, walks down the left side of the rects stair-stepping as necessary when the left bound changes, accross the bottom, back up the right side, and then finally accross the top. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Algorithmic Bias</title>
      <link>https://shadowfacts.net/2020/algorithmic-bias/</link>
      <category>misc</category>
      <category>social media</category>
      <guid>https://shadowfacts.net/2020/algorithmic-bias/</guid>
      <pubDate>Fri, 05 Jun 2020 13:55:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I am subscribed to Marques Brownlee on YouTube. I watch almost every one of his videos. YouTube is smart. It knows this, it recommends me almost all of his videos. But not this one. No matter how many times I refresh the page. No matter how far down the page I scroll. Despite the fact that the video has gotten 2.3 million views in 16 hours, performing better than a number of his recent videos. Despite the fact that it’s recommending me videos that are from people I am not subscribed to, videos that are years old, videos that I have watched before, videos that are about politics, videos that are about the ongoing Black Lives Matter protests in the wake of George Floyd’s murder.</p>
<p>This is what algorithmic bias looks like. <strong>Algorithms are not neutral.</strong><sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup></p>
<figure>
	<img src="/2020/algorithmic-bias/youtube_thumb.png" alt="YouTube thumbnail of an MKBHD video">
	<figcaption>A screenshot of the thumbnail for a YouTube video from MKBHD titled "<a href="https://www.youtube.com/watch?v=o-_WXXVye3Y" data-no-link-decoration>Reflecting on the Color of My Skin</a>".</figcaption>
</figure>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>“Algorithm” is a word here used not in the purely computer science sense, but to mean a element of software which operates in a black box, often with a machine learning component, with little or no human supervision, input, or control. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Switching to Vim</title>
      <link>https://shadowfacts.net/2020/switching-to-vim/</link>
      <category>editors</category>
      <guid>https://shadowfacts.net/2020/switching-to-vim/</guid>
      <pubDate>Thu, 21 May 2020 22:22:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>At the beginning of last summer, for reasons that I can no longer recall, I set a goal for myself to learn Vim over that summer. Now that summer is over and almost here again, I wanted to reflect on that process and whether I achieved my goal. By no means have I mastered Vim or am a Vim expert, but I feel reasonably proficient. I use Vim itself on the command line and in GUI form, as well as Vim bindings/plugins in all my IDEs. It has gotten so strongly ingrained into my muscle memory that I now find myself hitting ESC to exit insert mode in text boxes in my web browser and typing Vim commands into word processors.</p>
<!-- excerpt-end -->
<p>In order to force myself to try and become more proficient and get the keybindings worked into my muscle memory, I made the decision fairly early on (shortly after I felt comfortable with the basics of text navigation and editing) to switch all of my IDEs to use Vim keybindings/plugins and use it full time.<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup></p>
<p>I briefly considered using Vim independently of any IDE and relying entirely on plugins and command line tools to provide the rest of the functionality of the IDE, but I didn’t end up doing this for a couple reasons. First and foremost, a great deal of the functionality the IDEs provide is not easily replicable. Secondly, I didn’t want to have to learn even more things simultaneously. I wanted to stay focused on learning Vim, and adding a whole new set of tooling on top of that would distract from my main objective.</p>
<p>While I use Vim independently on some things, for most of the projects I work on both personally and for my job, I use an IDE. The ones I use most frequently are RubyMine for work and Xcode for personal iOS projects (and occasionally others in the JetBrains family, like IDEA and WebStorm).</p>
<p>The JetBrains IDEs sport a truly wonderful first-party plugin called <a href="https://github.com/JetBrains/ideavim" data-link="github.com/JetBrains/ideavim">IdeaVim</a> which emulates a great deal of Vim. In my experience using it the only feature of Vim that I’ve found IdeaVim does not also support is user-defined text objects (although all of the ones I use in Vim itself are supported already by IdeaVim).</p>
<p>In my experience using it, I have not encountered a single feature of Vim that IdeaVim does not support (granted, I’m not doing anything that crazily complicated).</p>
<p>For Xcode, there is a third-party plugin called <a href="https://github.com/XVimProject/XVim2/" data-link="github.com/XVimProject/XVim2/">XVim</a> which performs a similar function to IdeaVim. Unfortunately, XVim is nowhere near as full-featured or as comprehensive as IdeaVim. The feature I use most frequently which it doesn’t support is using the <code>.</code> command to repeat a command involving a text object (e.g. <code>ciwFoo&lt;ESC&gt;</code>). It either crashes Xcode or performs some sequence of actions that seems to bear no relation to my actual command. In environments where it is supported, I’ve found it very useful for pulling out bits of text that I recently deleted. Another of XVim’s shortcomings compared to IdeaVim is the fact that, in order to use it, you must have a copy of Xcode that has been re-codesigned with your own self-signed certificate. When Xcode is signed by Apple, it does not load third party plugins. This means I end up having multiple copies of Xcode on my disk (since I prefer to keep the Apple-signed ones and an old version around just in case. I currently have 4 copies of Xcode, for a total of 73.76 GB on disk). I’ve considered switching to AppCode, JetBrains’ IDE for iOS/macOS apps, but on the brief occasions I’ve tried it, it’s been rather lacking compared to Xcode and so not worth making the switch.<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup></p>
<p>For the projects I work on which I don’t use an IDE for (primarily Elixir projects), I use <a href="https://github.com/macvim-dev/macvim" data-link="github.com/macvim-dev/macvim">MacVim</a> with an assortment of plugins. I use <a href="https://github.com/scrooloose/nerdtree" data-link="github.com/scrooloose/nerdtree">NERDTree</a> as a file manager, <a href="https://github.com/junegunn/fzf" data-link="github.com/junegunn/fzf">fzf</a> for quickly switching files, <a href="https://github.com/neoclide/coc.nvim" data-link="github.com/neoclide/coc.nvim">coc.nvim</a> for integration with language servers (including Elixir and TypeScript), and a few other small plugins.</p>
<p>At this point, I could go on about how switching to Vim has vastly increased my productivity and turned me into the mythic 10x developer. But that isn’t true. I can only imagine switching to Vim has made little, if any, difference to my efficiency. Sure, when I’m typing or changing large sections of text, I feel faster. But the fact is, the vast majority of the time, I’m not doing that. The bottleneck is not my hands or my keyboards, it’s my brain. And Vim can’t magically change that.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Since I’ve gone all in on Vim, I also switched to rebinding the Caps Lock key to Escape on all my computers and keyboards. But I don’t just use it in Vim, I use the Caps Lock key as Esc in macOS for everyday things, and even games. A side effect of which has been I now find it infuriating to use computers where Caps Lock has not been remapped Escape because I press it instinctively and end up changing case and getting confused all too frequently. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>I mostly remember to not press <code>.</code> in normal mode. Xcode only crashes a couple times per week. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>The Sorry State of Thunderbolt 3 Docks</title>
      <link>https://shadowfacts.net/2020/thunderbolt-3/</link>
      <category>computers</category>
      <guid>https://shadowfacts.net/2020/thunderbolt-3/</guid>
      <pubDate>Mon, 13 Apr 2020 21:19:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>My primary computer is a 2019 16“ MacBook Pro. It has four ports. All of which are USB-C/Thunderbolt 3. Enough words by enough people have been expended complaining about how the lack of common ports makes their lives more difficult, so instead, I’m going to complain about how the solutions for connecting non-USB-C peripherals are awful. This is something I’ve ranted about multiple times on the fediverse, since it’s something you’d think would be a solved problem by now. But clearly it isn’t, so here we go again.</p>
<!-- excerpt-end -->
<p>I’ve got a pair of monitors connected to my laptop, one of which uses Mini DisplayPort and the other of which uses HDMI. Currently, I’ve got a USB-C → MiniDP dongle as well as a USB-C → HDMI (and several other things) dongle. Unfortunately, they’re both rather flaky. Every other time (roughly), when my laptop wakes from sleep, the MiniDP dongle won’t work and I’ll have to disconnect/reconnect it to get video output to that monitor. Completely unrelaed, the HDMI dongle will randomly (often multiple times a day) start outputting incorrectly. The image will wrap around by about 25% of the width of the screen, resulting in the left edge of the picture being displayed about a quarter of the way over the screen, and the rightmost quarter of the picture displaying on the leftmost quarter of the screen. Similarly, the fix for this is to disconnect and reconnect the HDMI cable. In the past three days, the two adapters have flaked out a combined 11 times. This has become particularly annoying as, given the current state of things, I’m working from home through at least mid-June. Meaning I’m using my computer far more than I otherwise would.</p>
<h2 id="a-survey-of-bad-options"><a href="#a-survey-of-bad-options" class="header-anchor" aria-hidden="true" role="presentation">##</a> A Survey of Bad Options</h2>
<p>“Thunderbolt™ 3 – The USB-C That Does It All” boldly proclaims an <a href="https://thunderbolttechnology.net/blog/thunderbolt-3-usb-c-does-it-all" data-link="thunderbolttechnology.net/blog/thunderbo…">Intel press release</a> from June 2015. So, let’s see exactly how many choice there are that can Do It All. The ports I’m looking for are: two video outputs (preferably one DisplayPort and one HDMI), two USB type-A, and ethernet. Power delivery would be nice, but isn’t a requirement. Not too much to ask, right?</p>
<h3 id="option-1-250-"><a href="#option-1-250-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 1 - $250 <!-- https://www.caldigit.com/ts3-plus/ --></h3>
<ul>
<li>USB-C, Thunderbolt 3, host connection, 87W USB-PD</li>
<li>USB-C, 3.1 Gen 1, data only</li>
<li>USB-C, 3.1 Gen 2, data only</li>
<li>USB-C, Thunderbolt 3, downstream daisy-chaining</li>
<li>5x USB-A, 3.1 Gen 1</li>
<li>Gigabit Ethernet</li>
<li>DisplayPort</li>
<li>SD Card</li>
<li>3.5mm audio in/out</li>
<li>S/PDIF</li>
</ul>
<p>This would require a DisplayPort → Mini DisplayPort adapter (this is the least of my concerns, since it could at least operate passively since MiniDP is just a different physical connector for the same protocol) as well as a USB-C → HDMI adapter, the very thing I’m trying to get away from. It also only provides 87W of power delivery, 9 watts less than the charger than comes in the box with the MacBook Pro. While plugging in the laptop’s own charger isn’t a big deal, I don’t want to pay several hundred dollars for a device most of whose capabilities I would not be using.</p>
<h3 id="option-2-200-"><a href="#option-2-200-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 2 - $200 <!-- https://www.caldigit.com/usb-c-pro-dock/ --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection, 85W USB-PD</li>
<li>2x DisplayPort</li>
<li>3x USB-A, 3.2 Gen 1</li>
<li>1x USB-C, 3.2 Gen 2</li>
<li>SD Card</li>
<li>Gigabit Ethernet</li>
<li>3.5mm audio in/out</li>
</ul>
<p>Apparently, the lack of a USB-C port, two USB-A ports, S/PDIF, and two whole watts of power delivery save you $50 since this is from the same manufacturer as option 1. This option would also require chaining dongles (either USB-C → HDMI or DisplayPort → HDMI) and may not be able to fully power the laptop when under full load.</p>
<h3 id="option-3-150-"><a href="#option-3-150-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 3 - $150 <!-- https://www.caldigit.com/mini-dock/ --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection</li>
<li>Gigabit Ethernet</li>
<li>Either of:
<ul>
<li>2x DisplayPort and 1x USB-A 3.0</li>
<li>2x HDMI, 1x USB-A 3.0, and 1x USB-A 2.0</li>
</ul>
</li>
</ul>
<p>Yes, the HDMI model has an extra USB type A 2.0 port for reasons I cannot find elucidated anywhere on the manufacturer’s website (I would assume bandwidth limitations, but the manufacturer claims both types of video connections support up to 4096x2160 @ 60Hz, so who knows). Either model would require me to adapt either DisplayPort → HDMI or HDMI → DisplayPort, and chaining video adapters is something I wish to avoid. In any case, a single USB-A port is not enough to connect all of my peripherals, meaning I would need yet another dongle. This also has no external power input or power delivery, so I would still be using three of my four ports.</p>
<h3 id="option-4-250-"><a href="#option-4-250-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 4 - $250 <!-- https://store.hp.com/us/en/pdp/hp-thunderbolt-dock-120w-g2 --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection</li>
<li>USB-C port of indeterminate capabilities</li>
<li>USB-C that supports DisplayPort</li>
<li>3x USB-A</li>
<li>2x DisplayPort</li>
<li>Gigabit Ethernet</li>
<li>3.5mm audio</li>
<li>VGA</li>
</ul>
<p>And so we arrive at the first option that can do 100W power delivery. Of course, this would still require a DisplayPort → HDMI adapter. This one has particularly mixed reviews as to whether it works with Macs or not (they all have mixed reviews about reliability, but this one especially so). Some claim it doesn’t support multiple monitors since Macs don’t support DisplayPort Multi-Stream Transport.</p>
<h3 id="option-5-300-"><a href="#option-5-300-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 5 - $300 <!-- https://www.lenovo.com/us/en/accessories-and-monitors/home-office/Thunderbolt-Dock-Gen-2-US/p/40AN0135US --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection, 90W USB-PD</li>
<li>USB-C, Thunderbolt 3</li>
<li>2x pairs of DisplayPort/HDMI, only one of which is usable at a time</li>
<li>5x USB-A, 3.1 Gen 2</li>
<li>Gigabit Ethernet</li>
<li>3.5mm audio</li>
</ul>
<p>Using multiple  of the video outputs requires Multi-Stream Transport support, meaning the sole USB-C port would need to be connected to a USB-C → HDMI/DisplayPort dongle to get a second video output (this somehow bypasses the MST requirement?). Also, apparently both the ethernet and audio are not supported under macOS.</p>
<h3 id="option-6-240-"><a href="#option-6-240-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 6 - $240 <!-- https://www.cablematters.com/pc-887-130-certified-aluminum-thunderbolt-3-docking-station-with-dual-4k-60hz-video-and-60w-power-delivery.aspx --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection, 60W USB-PD</li>
<li>USB-C, Thunderbolt 3</li>
<li>5x USB-A 3.0</li>
<li>HDMI</li>
<li>Gigabit Ethernet</li>
<li>SD Card</li>
</ul>
<p>Once again, a USB-C → DisplayPort dongle would be needed, and the builtin power delivery isn’t anywhere near sufficient.</p>
<h3 id="option-7-150-"><a href="#option-7-150-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 7 - $150 <!-- https://www.elgato.com/en/dock/thunderbolt-3-mini --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection</li>
<li>DisplayPort</li>
<li>HDMI</li>
<li>USB-A 3.1</li>
<li>Gigabit Ethernet</li>
</ul>
<p>The first option that has both a DisplayPort and HDMI connection. Of course, doesn’t have enough USB-A ports, or any power delivery. Despite it’s lack of ports, it would seem like one of the better options. But, I can’t find any concrete information on the internet about whether or not it supports outputting to two displays simultaneously without mirroring on macOS (meaning whether MST support is required or not).</p>
<h3 id="option-8-400-"><a href="#option-8-400-" class="header-anchor" aria-hidden="true" role="presentation">###</a> Option 8 - $400 <!-- https://www.razer.com/gaming-laptops/razer-core-x --></h3>
<ul>
<li>USB-C, Thunderbolt 3 host connection, 100W USB-PD</li>
<li>However many video outputs I want</li>
</ul>
<p>The final option is an eGPU enclosure. This one does support power delivery, enough to sustain my laptop under full load. I have an RX580 from my old Hackintosh which: has native support in macOS, is comparable to the dedicated GPU in my laptop, and has plenty of video outputs (DisplayPort, HDMI, and DVI-D). Of course, it doesn’t have any additional ports (there are models which also provide USB-A and Ethernet, but the graphics card alone will consume all 40Gbps of bandwidth Thunderbolt 3 has, so adding anything more will bottleneck it and cause stability to suffer). While somewhat tempting (the idea of upgrading to a beefier graphics card is interesting), $400 is an abusrdly high cost for what amounts to: a box, a cheap fan, a cheap power supply, and a Thunderbolt controller chip.</p>
<p>There are even more options I haven’t bothered to list since they have pretty much the exact same port selection and set of trade-offs as one of these. Even the ones that are the least bad are still very expensive for what they offer. There are even more constraints, because macOS support (even putting aside MST) is by no means a given. The ones I feel most confident about are the only brand that’s sold through Apple’s own store (and also some of the most expensive).</p>
<h2 id="a-historical-perspective"><a href="#a-historical-perspective" class="header-anchor" aria-hidden="true" role="presentation">##</a> A Historical Perspective</h2>
<p>None of these options entirely meet my desire set of capabilities, so clearly something that does is impossible, right? There’s absolutely no way a device could exist that connects to a computer, provides power to it, allows it to output multiple video signals, hooks it up to several peripherals, and connects it to a hard-wired network, right? Such an astounding assortment of ports, such a triumph of technology, and miracle of modern engineering this fantastical gizmo would be, that it couldn’t possibly exist. Right?</p>
<p>Thunderbolt 3 was first implemented in consumer products in 2015. A full 3 years earlier, in 2012, Lenovo had released the <a href="https://support.lenovo.com/us/en/solutions/migr-74447" data-link="support.lenovo.com/us/en/solutions/migr-…">“ThinkPad Mini Dock Plus Series 3”</a>, which, in addition to an unwieldy name, has a stunning variety of ports, the likes of which the present day can only dream of. It has six USB ports; five video outputs (up to three of which can be used simultaneously), including DisplayPort, DVI-D, and VGA; audio in/out; eSATA; ethernet; and a 135W charger. Compare that to even the best, most expensive options I listed above. Quite a stark contrast. Granted, the ThinkPad dock uses a proprietary connector and doesn’t haven’t to care about the vastly higher bandwidth standards that modern docks do. But, accepting the different times they were released, the now 8 year old ThinkPad dock is more versatile and more useful in every way<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. For my exact use case of a couple of displays, a few USB peripherals, a wired network connection, and power, the ThinkPad dock would have been more than sufficient.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>So, has the magnificent pairing of Thunderbolt 3 and USB Type-C delivered on its promise to be the one port that does it all? In my estimation: no, absolutely not. There’s no one dock that meets my needs, and of the options that don’t fulfill my requirements, there isn’t a single one that is obviously the least bad. What happened? How did we go from these behemoths of connectivity to not only laptops, but docks, with vastly fewer ports. Even Lenovo’s own current generation ThinkPad docks, which are based on USB-C and Thunderbolt, offer far fewer connections than 8 years ago. Maybe USB 4 (which has subsumed Thunderbolt) will fix things. Or maybe it will just introduce a whole new set of confusing constraints and restrictions to keep track of.</p>
<p>I don’t know what I’m going to end up doing. There are so many different choices with a such huge variety of trade-offs that keeping them all in my head at once and trying to make a decision is rather difficult. Who knows, maybe I’ll just stick with my broken USB-C dongles.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>It may not have an SD card reader, as is common today, but the <a href="https://support.lenovo.com/us/en/solutions/pd003320" data-link="support.lenovo.com/us/en/solutions/pd003…">ThinkPad W510</a> it was designed to complement had one built it, along with a myriad of other ports. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Writing a JavaScript Syntax Highlighter in Swift</title>
      <link>https://shadowfacts.net/2020/syntax-highlighting-javascript/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/syntax-highlighting-javascript/</guid>
      <pubDate>Thu, 09 Apr 2020 15:48:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>For <a href="https://git.shadowfacts.net/shadowfacts/MongoView" data-link="git.shadowfacts.net/shadowfacts/MongoVie…">a project</a> I’m currently working on, I need to display some JavaScript code<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>, and because I’m a perfectionist, I want it to be nice and pretty and display it with syntax highlighting. Originally, I was planning to use John Sundell’s <a href="https://github.com/JohnSundell/Splash" data-link="github.com/JohnSundell/Splash">Splash</a> Swift syntax highlighting library (both a “(Swift syntax) highlighting library” and a “Swift (syntax highlighting) library”). It can already render to my desired output format, an <code>NSAttributedString</code>, and it has an interface for defining new grammars, which I thought would make it relatively easy to extend to support JavaScript. After getting started, it quickly  became apparent that it wouldn’t be quite so easy. In addition to writing all the code to parse JavaScript, I’d have to go through the Splash codebase and understand a decent amount about how it works. This grew uninteresting pretty quickly, so I decided I would try just writing everything myself. My highlighting needs were fairly simple, how hard could it be?</p>
<!-- excerpt-end -->
<p>The actual parse loop is fairly straightforward: it starts at the beginning of the string and tries to parse statements until it reaches the end of the string. Parsing a statement means looking at the next character, and depending what it looks like trying to parse something of that type. If it starts with a single or double quote, it tries to parse a string literal, if it starts with a digit, it tries to parse a number literal, if it starts with an alphabetical character, it tries to parse an identifier, and so on. Most of the things that can be parsed aren’t all that complicated. The most difficult are template, object, and array literals all because they can all contain further expressions and you need to be careful when recursing to be sure that when parsing the inner expression, you don’t start consuming part of the outer thing.</p>
<p>One simplifying factor is that there are a number of things my highlighter intentionally doesn’t handle, including keywords and block statements. The main reason is I expect those to come up rarely, if ever, in the context I’m using this in. I also purposely didn’t touch a bunch of other things that an actual JavaScript parser/interpreter would have to be concerned with in order to actually execute code. At the top of that list is things like automatic semicolon insertion (JavaScript’s weird way of making semicolons optional), and operator precedence, since they have no effect on the highlighted output.</p>
<p>One of the more annoying parts, completely unrelated to JavaScript, is dealing with strings in Swift. Sure Swift’s handling of strings is totally safe and correct, but it’s an absolute pain in the ass to use. <em>Want to get the fifth character in a string? Just use <code>string[string.index(string.startIndex, offsetBy: 5)]</code>, it’s super simple!</em> So, the highlighter keeps track of <code>String.Index</code> internally and has several helper methods for moving around within the string. Furthermore, the CharacterSet class is weird and doesn’t work the way you’d expect. Because it’s bridged from Objective-C, its <code>contains(_:)</code> method doesn’t take a Swift <code>Character</code>, it takes a <code>Unicode.Scalar</code>. Because of this, the entire highlighter doesn’t care about characters as Swift views them, it only cares about Unicode scalars, using the string’s <code>String.UnicodeScalarView</code>.</p>
<p>Also, this may be the first time I’ve ever used while/let in Swift. The peek function returns the next character in the string, or <code>nil</code>, if there are none remaining, so, with while/let, consuming all characters in a set is as simple as:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">while let</span> char = <span class="hl-fn">peek</span>(),
		<span class="hl-type">CharacterSet</span>.<span class="hl-prop">whitespacesAndNewlines</span>.<span class="hl-fn">contains</span>(char) {
	<span class="hl-fn">consume</span>()
}
</code></pre>
<p>I spent a couple days profiling it, trying to improve the performance to a point where it’s usable for live-highlighting a decently large file. Right now, a full rehighlight of a 1200 line JSON object takes around 10 ms, which, while not spectacularly fast, is fast enough that there’s not appreciable latency while typing. One of the single biggest changes I made was to ensure that I’m only ever using the string’s Unicode scalar view. Just going from <code>string[currentIndex] == &quot;\\&quot;</code> to <code>string.unicodeScalars[currentIndex] == &quot;\\&quot;</code> in the JS-string handling code resulted in an 8 ms improvement. Another performance-driven change I made, though not to the syntax highlighter itself, was to try and only rehighlight when absolutely necessary. For the most common operations, typing or deleting a single character, I find the token that is being modified, and, if the added/removed character wouldn’t cause a structural change to the rest of the text (e.g., inserting a character inside of a string), I can alter the length of the modified token and shift the locations of all subsequent tokens. This takes about 70 μs for deleting a single character and 130 μs for inserting a single character. Inserting, I think (but haven’t verified), takes so much longer because I also have to add an attribute to the attributed string for the newly inserted character, which kicks off a bunch of work inside the text view.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>If you’d asked me a year ago, heck, even a couple months ago, if I’d ever think about undertaking a project like this myself, I’d have said absolutely not and proceeded to go find a third party library that could do the job adequately. But recently, I’ve been watching <a href="https://youtu.be/MnctEW1oL-E" data-link="youtu.be/MnctEW1oL-E">Jonathan Blow</a> talk about building parsers and <a href="https://youtu.be/byNwCHc_IIM" data-link="youtu.be/byNwCHc_IIM">Andreas Kling</a> actually build a JavaScript interpreter starting from scratch, and there’s one thing that they both mentioned on multiple occasions that really stuck with me: it’s just code. Sure, its input is source code, but the operations it performs to produce syntax highlighted output aren’t anything insanely complicated or out of the reach of any reasonably experienced programmer.</p>
<p>I’m not trying to claim that what I’ve written is anywhere near as complicated as a full-blown parser or interpreter that could be used to execute code. Nor is it a simple one.</p>
<p>But it is one that, not too long ago, I wouldn’t have willingly undertaken. Parsers, particularly parsers for programming language source code have this perception that only the best of the best can build that because they’re so incredibly complicated. And that’s not true at all. Sure, they’re complex programs, because the problem they’re solving is non-trivial. But the way you go about solving it isn’t insanely difficult, doesn’t require any specialized knowledge, and doesn’t use any uncommon techniques. The most important thing is breaking down one big problem (“how do you parse source code?”) into smaller and smaller chunks that can  be solved individually and then combined together.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Actually, some <a href="/2020/faking-mongo-eval/" data-link="/2020/faking-mongo-eval/">not JavaScript code</a> that looks for all intents and purposes like JavaScript code, so highlighting it is the same. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Simple Swift Promises</title>
      <link>https://shadowfacts.net/2020/simple-swift-promises/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/simple-swift-promises/</guid>
      <pubDate>Wed, 19 Feb 2020 02:10:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Recently, I’ve been working on cleaning up the networking code in Tusker, my iOS client for Mastodon/Pleroma and I briefly played around with using the new <a href="https://developer.apple.com/documentation/combine" data-link="developer.apple.com/documentation/combin…">Combine</a> framework as well as the built in <code>URLSession.DataTaskPublisher</code> helper. Combine, much like SwiftUI, uses Swift’s incredibly powerful type system to great advantage because it’s a Swift-only framework. It’s quite efficient, but because there are so many generic types and implementations of different protocols, the API (in my experience) isn’t the most pleasant to work with. I was thinking about other asynchronous programming schemes and the one that came to mind as being the nicest to use was JavaScript’s Promises. It has a fairly simple API, so I started wondering how much work it would be to build something similar in Swift. Turns out: not that much.</p>
<!-- excerpt-end -->
<p>Be warned, this code isn’t great. It’s the result of a few hours of fiddling around trying to build something, not the best possible solution.</p>
<p>To start off with, there’s a <code>Promise</code> class that’s generic over its result type. It stores 1) a list of closures that will be invoked when it is resolved and 2) the resolved result (or <code>nil</code>, if the promise hasn’t yet been resolved). There’s a helper function that resolves the promise by storing the result and invokes any already-added completion handlers with the result. There’s another function that’s called to add a handler to the promise, storing it if the promise hasn’t been resolved and invoking it immediately if it has.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public class</span> Promise&lt;Result&gt; {
	<span class="hl-kw">private var</span> handlers: [(<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Void</span>] = []
	<span class="hl-kw">private var</span> result: <span class="hl-type">Result</span>?

	<span class="hl-kw">func</span> resolve(<span class="hl-kw">_</span> result: <span class="hl-type">Result</span>) {
		<span class="hl-kw">self</span>.<span class="hl-prop">result</span> = result
		<span class="hl-kw">self</span>.<span class="hl-prop">handlers</span>.<span class="hl-fn">forEach</span> { $0(result) }
	}

	<span class="hl-kw">func</span> addHandler(<span class="hl-kw">_</span> handler: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Void</span>) {
		<span class="hl-kw">if let</span> result = result {
			<span class="hl-fn">handler</span>(result)
		} <span class="hl-kw">else</span> {
			handlers.<span class="hl-fn">append</span>(handler)
		}
	}
}
</code></pre>
<p>To keep things clean, everything in the public API is implemented in a public extension on <code>Promise</code>. To start with, the most primitive way of constructing a promise. The initializer takes a closure (<code>resultProvider</code>) which itself receives as an argument a closure that takes a <code>Result</code>. In the initializer, the result provider is immediately invoked passing the <code>self.resolve</code> helper function from earlier. This will kick off whatever potentially long-running/asynchronous task is being wrapped in a promise. Once the task has completed, it will call the closure passed in as the <code>resolve</code> parameter with whatever value it ultimately got.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">convenience init</span>(resultProvider: <span class="hl-kw">@escaping</span> (<span class="hl-kw">_</span> resolve: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Void</span>) -&gt; <span class="hl-type">Void</span>) {
		<span class="hl-kw">self</span>.<span class="hl-kw">init</span>()
		<span class="hl-fn">resultProvider</span>(<span class="hl-kw">self</span>.<span class="hl-prop">resolve</span>)
	}
}
</code></pre>
<p>Using it might be something like this:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> promise = <span class="hl-type">Promise</span>&lt;<span class="hl-type">String</span>&gt; { (resolve) <span class="hl-kw">in</span>
	<span class="hl-fn">performLongOperation</span>() { (result) <span class="hl-kw">in</span>
		<span class="hl-fn">resolve</span>(result)
	}
}
</code></pre>
<p>With this in place, the first helper function can be implemented. It will take a single value and produce a promise that has that is resolved with that value:</p>
<pre class="highlight" data-lang=""><code>public extension Promise {
	static func resolve&lt;Result&gt;(_ value: Result) -&gt; Promise&lt;Result&gt; {
		let promise = Promise&lt;Result&gt;()
		promise.resolve(value)
		return promise
	}
}
</code></pre>
<p>Using it is as simple as <code>Promise.resolve(&quot;blah&quot;)</code>. (The only reason this is a static method instead of just another convenience initializer on Promise is to match the JavaScript API that it’s modeled after.)</p>
<p>Next up, there needs to be a way of adding a completion block to a promise. There are a couple different possibilities for using this and each will be implemented slightly differently.</p>
<p>The first and simplest is adding a completion block that receives the result of the promise and doesn’t return anything. Another <code>then</code> implementation takes a closure that receives the value of the promise and produces a new value, resulting in a promise that produces the new value. Finally, there’s one that takes a closure which produces a promise of a new value, resulting in a promise that returns the new value.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">@discardableResult
	func</span> then(<span class="hl-kw">_</span> fn: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Void</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-fn">addHandler</span>(fn)
		<span class="hl-kw">return self</span>
	}

	<span class="hl-kw">func</span> then&lt;Next&gt;(<span class="hl-kw">_</span> mapper: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Next</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;()
		<span class="hl-fn">addHandler</span> { (parentResult) <span class="hl-kw">in
			let</span> newResult = <span class="hl-fn">mapper</span>(parentResult)
			next.<span class="hl-fn">resolve</span>(newResult)
		}
		<span class="hl-kw">return</span> next
	}

	<span class="hl-kw">func</span> then&lt;Next&gt;(<span class="hl-kw">_</span> mapper: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;()
		<span class="hl-fn">addHandler</span> { (parentResult) <span class="hl-kw">in
			let</span> newPromise = <span class="hl-fn">mapper</span>(parentResult)
			newPromise.<span class="hl-fn">addHandler</span>(next.<span class="hl-prop">resolve</span>)
		}
		<span class="hl-kw">return</span> next
	}
}
</code></pre>
<p>In the simplest case, the promise can simply add the handler to itself and return itself for other uses. This is marked with <code>@discardableResult</code> because the API user should be able to add a completion handler without causing a unnecessary compile-time warning.</p>
<p>When given a closure that produces a value, <code>then</code> should return a new promise that’s for the type of the result of the closure. To achieve this, the <code>then</code> function is generic for the <code>Next</code> type which is both the return type of the closure and the result type of the promise returned by <code>then</code>. A new promise is constructed, and a completion handler is added to <code>self</code> to resolve the next promise once self has resolved with the value produced by passing its own result through the mapper function.</p>
<p>Finally, when given a closure that produces a promise, a new promise is also constructed and a completion handler added to the current promise. This time, when the parent result is passed into the mapper function, it receives back a promise. A completion handler is added to that promise which resolves the next promise with the value it produces, linking the promise returned by <code>then</code> onto the promise produced by the closure. This version of <code>then</code> in particular is very powerful, because it allows promises to composed together and sets of nested callbacks collapsed.</p>
<p>And with that, a barebones promises API is born.</p>
<h2 id="handling-errors"><a href="#handling-errors" class="header-anchor" aria-hidden="true" role="presentation">##</a> Handling Errors</h2>
<p>This promise implementation can fairly easily be extended to support handling errors in much the same manner as normal results (a lot of the code will look very familiar).</p>
<p>Promise could be made generic over some failure type as well, but using the plain Swift <code>Error</code> type makes things a bit simpler.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public class</span> Promise&lt;Result&gt; {
	<span class="hl-kw">private var</span> catchers: [(<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Void</span>] = []
	<span class="hl-kw">private var</span> error: <span class="hl-type">Error</span>?

	<span class="hl-kw">func</span> reject(<span class="hl-kw">_</span> error: <span class="hl-type">Error</span>) {
		<span class="hl-kw">self</span>.<span class="hl-prop">error</span> = error
		<span class="hl-kw">self</span>.<span class="hl-prop">catchers</span>.<span class="hl-fn">forEach</span> { $0(error) }
	}

	<span class="hl-kw">func</span> addCatcher(<span class="hl-kw">_</span> catcher: <span class="hl-kw">@escaping</span> (<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Void</span>) {
		<span class="hl-kw">if let</span> error = error {
			<span class="hl-fn">catcher</span>(error)
		} <span class="hl-kw">else</span> {
			catchers.<span class="hl-fn">append</span>(catcher)
		}
	}
}
</code></pre>
<p>Similarly to the normal promise resolution stuff, the Promise class stores a list of functions which handle any error as well as the error itself, if one’s already been produced. There’s also a <code>reject</code> internal helper function which is called to reject the promise, storing the error and passing it to any already-registered catch functions. Also paralleling the <code>addHandler</code> method, there’s an <code>addCatcher</code> helper which takes a closure that consumes an error, either invoking it immediately if the promise has already been rejected or appending it to the internal array of catcher functions.</p>
<p>The main convenience initializer is also amended to receive a closure that itself takes two closure parameters: functions that respectively resolve and reject the promise. The closure is invoked immediately passing <code>self.resolve</code> and <code>self.reject</code> as the resolver and rejecter functions.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">convenience init</span>(resultProvider: <span class="hl-kw">@escaping</span> (<span class="hl-kw">_</span> resolve: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Void</span>, <span class="hl-kw">_</span> reject: <span class="hl-kw">@escaping</span> (<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Void</span>) -&gt; <span class="hl-type">Void</span>) {
		<span class="hl-kw">self</span>.<span class="hl-kw">init</span>()
		<span class="hl-fn">resultProvider</span>(<span class="hl-kw">self</span>.<span class="hl-prop">resolve</span>, <span class="hl-kw">self</span>.<span class="hl-prop">reject</span>)
	}
}
</code></pre>
<p>With that in place, a static <code>reject</code> helper can also be created, which works almost exactly the same as the static <code>resolve</code> helper. It takes an error and produces a promise that’s rejected with that error by immediately invoking the <code>reject</code> function with that error in the result provider closure.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">static func</span> reject&lt;Result&gt;(<span class="hl-kw">_</span> error: <span class="hl-type">Error</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-kw">let</span> promise = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt;()
		promise.<span class="hl-fn">reject</span>(error)
		<span class="hl-kw">return</span> promise
	}
}
</code></pre>
<p>Additionally, the two <code>then</code> functions that produce new promises are changed to make them reject the next promise when they themself reject. The one that accepts a closure returning a promise is also tweaked so that, when the new promise is received from the closure, the next promise is made to fail if that promise fails.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">func</span> then&lt;Next&gt;(<span class="hl-kw">_</span> mapper: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Next</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;()
		<span class="hl-fn">addHandler</span> { (parentResult) <span class="hl-kw">in
			let</span> newResult = <span class="hl-fn">mapper</span>(parentResult)
			next.<span class="hl-fn">resolve</span>(newResult)
		}
		<span class="hl-fn">addCatcher</span>(next.<span class="hl-prop">reject</span>)
		<span class="hl-kw">return</span> next
	}

	<span class="hl-kw">func</span> then&lt;Next&gt;(<span class="hl-kw">_</span> mapper: <span class="hl-kw">@escaping</span> (<span class="hl-type">Result</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Next</span>&gt;()
		<span class="hl-fn">addHandler</span> { (parentResult) <span class="hl-kw">in
			let</span> newPromise = <span class="hl-fn">mapper</span>(parentResult)
			newPromise.<span class="hl-fn">addHandler</span>(next.<span class="hl-prop">resolve</span>)
			newPromise.<span class="hl-fn">addCatcher</span>(next.<span class="hl-prop">reject</span>)
		}
		<span class="hl-fn">addCatcher</span>(next.<span class="hl-prop">reject</span>)
		<span class="hl-kw">return</span> next
	}
}
</code></pre>
<p>Next, for actually handling errors there are public <code>catch</code> functions on <code>Promise</code> in the same fashion as <code>then</code>:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">@discardableResult
	func</span> `catch`(<span class="hl-kw">_</span> catcher: <span class="hl-kw">@escaping</span> (<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Void</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-fn">addCatcher</span>(catcher)
		<span class="hl-kw">return self</span>
	}

	<span class="hl-kw">func</span> `catch`(<span class="hl-kw">_</span> catcher: <span class="hl-kw">@escaping</span> (<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Result</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt;()
		<span class="hl-fn">addHandler</span>(next.<span class="hl-prop">resolve</span>)
		<span class="hl-fn">addCatcher</span> { (error) <span class="hl-kw">in
			let</span> newResult = <span class="hl-fn">catcher</span>(error)
			next.<span class="hl-fn">resolve</span>(newResult)
		}
		<span class="hl-kw">return</span> next
	}

	<span class="hl-kw">func</span> `catch`(<span class="hl-kw">_</span> catcher: <span class="hl-kw">@escaping</span> (<span class="hl-type">Error</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt;) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-kw">let</span> next = <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt;()
		<span class="hl-fn">addHandler</span>(next.<span class="hl-prop">resolve</span>)
		<span class="hl-fn">addCatcher</span> { (error) <span class="hl-kw">in
			let</span> newPromise = <span class="hl-fn">catcher</span>(error)
			newPromise.<span class="hl-fn">addHandler</span>(next.<span class="hl-prop">resolve</span>)
			newPromise.<span class="hl-fn">addCatcher</span>(next.<span class="hl-prop">reject</span>)
		}
		<span class="hl-kw">return</span> next
	}
}
</code></pre>
<p>The interesting implementations of the <code>catch</code> function both first add a handler to itself which simply resolves the next promise with the same result. They also add catchers to themselves which invoke the <code>catcher</code> closure with error produced by the parent and either gets a result back immediately, in which case it resolves the next promise, or gets back a promise for a new result, in which case it adds a handler to the new result promise to either resolves the next promise when the new promise succeeds or reject it if the new promise fails (that is, if a catcher function produces a promise that resolves, the parent’s error is resolved, or if it produces a promise that rejects, the parent’s error is replaced with the new error).</p>
<p>One difference between these functions and the <code>then</code> function, is that the result type of the parent promise must be the same as the new promise’s result type. This is done because JavaScript promises have the semantic where <code>then</code> handlers added after a <code>catch</code> are invoked regardless of whether or not the promise resolved (unless, of course, the catch block produced a rejected promise). This means that the catcher closure must produce a value of the same type as the parent promise, otherwise, there would be a case in which subsequent thens could not be invoked with the actual result value. That makes the following example, in which a potential error is replaced with some default value meaning <code>print</code> will always be invoked, possible:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-fn">longRunningPossiblyRejectingPromise</span>()
	.<span class="hl-fn">catch</span> { (error) -&gt; <span class="hl-type">String</span> <span class="hl-kw">in</span>
		<span class="hl-cmt">// log error</span>
		<span class="hl-kw">return</span> <span class="hl-str">"default value"</span>
	}.<span class="hl-fn">then</span> { (str) -&gt; <span class="hl-type">Void</span> <span class="hl-kw">in</span>
		<span class="hl-fn">print</span>(str)
	}
</code></pre>
<p>Now, the simple promise implementation is capable of handling errors as well.</p>
<h2 id="finishing-touches"><a href="#finishing-touches" class="header-anchor" aria-hidden="true" role="presentation">##</a> Finishing Touches</h2>
<p>First, because of the way promises are implemented, the queue a then/catch closure is executed on depends solely on the queue on which the previous promise resolved/rejected. To make switching queues easier, a simple helper function can be written that simply passes the result through, just resolving on a different queue.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">func</span> handle(on queue: <span class="hl-type">DispatchQueue</span>) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt; {
		<span class="hl-kw">return self</span>.<span class="hl-fn">then</span> { (result) <span class="hl-kw">in
			return</span> <span class="hl-type">Promise</span> { (resolve, reject) <span class="hl-kw">in</span>
				queue.<span class="hl-fn">async</span> {
					<span class="hl-fn">resolve</span>(result)
				}
			}
		}
	}
}
</code></pre>
<p>Next, the <code>Promise.all</code> helper function can be implemented using a <code>DispatchGroup</code> to take an array of promises with the same result type and create a new promise that resolves to an array of values of the same type as the result type:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">public extension</span> <span class="hl-type">Promise</span> {
	<span class="hl-kw">static func</span> all&lt;Result&gt;(<span class="hl-kw">_</span> promises: [<span class="hl-type">Promise</span>&lt;<span class="hl-type">Result</span>&gt;], queue: <span class="hl-type">DispatchQueue</span> = .<span class="hl-prop">main</span>) -&gt; <span class="hl-type">Promise</span>&lt;[<span class="hl-type">Result</span>]&gt; {
		<span class="hl-kw">let</span> group = <span class="hl-type">DispatchGroup</span>()

		<span class="hl-kw">var</span> results: [<span class="hl-type">Result</span>?](repeating: <span class="hl-kw">nil</span>, count: promises.<span class="hl-prop">count</span>)
		<span class="hl-kw">var</span> firstError: <span class="hl-type">Error</span>?

		<span class="hl-kw">for</span> (index, promise) <span class="hl-kw">in</span> promises.<span class="hl-fn">enumerated</span>() {
			group.<span class="hl-fn">enter</span>()
			promise.<span class="hl-fn">then</span> { (res) -&gt; <span class="hl-type">Void</span> <span class="hl-kw">in</span>
				queue.<span class="hl-fn">async</span> {
					results[index] = res
					group.<span class="hl-fn">leave</span>()
				}
			}.<span class="hl-kw">catch</span> { (err) -&gt; <span class="hl-type">Void</span> <span class="hl-kw">in
				if</span> firstError == <span class="hl-kw">nil</span> {
					firstError = err
				}
				group.<span class="hl-fn">leave</span>()
			}
		}

		<span class="hl-kw">return</span> <span class="hl-type">Promise</span>&lt;[<span class="hl-type">Result</span>]&gt; { (resovle, reject) <span class="hl-kw">in</span>
			group.<span class="hl-fn">notify</span>(queue: queue) {
				<span class="hl-kw">if let</span> firstError = firstError {
					<span class="hl-fn">reject</span>(firstError)
				} <span class="hl-kw">else</span> {
					<span class="hl-fn">resolve</span>(results.<span class="hl-fn">compactMap</span> { $0 })
				}
			}
		}
	}
}
</code></pre>
<p>This method follows the same semantics as the JavaScript equivalent. If any of the individual promises rejects, the <code>all</code> promise will be rejected with the first error that occurred. It also maintains the order of the results.</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>Promises can be pretty useful, but they’re not without their own pitfalls. Primarily, if you want to use the result of an intermediate promise in one further along the chain, you have to do something like passing it along with every intermediate result in a tuple, which is less than ideal. But, in some specific cases, they can be quite useful.</p>
<p>Consider making a new post in a social media app. First, any selected attachments are uploaded to the server. Then, only after all of those have completed successfully, can the post be made. After the post has completed, the resulting post received back from the API is stored. After that, UI changes can be made on the main thread to indicate that the post has succeeded. And, for all of those steps, there’s some common error handling code to show a message to the user. As in the following (simplified) example, this fits fairly well into the model of promises we’ve constructed.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> attachmentPromises = attachments.<span class="hl-fn">map</span> { (attachment) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Attachment</span>&gt; <span class="hl-kw">in</span>
	<span class="hl-type">ApiClient</span>.<span class="hl-prop">shared</span>.<span class="hl-fn">uploadAttachment</span>(attachment)
}
<span class="hl-type">Promise</span>&lt;[<span class="hl-type">Attachment</span>]&gt;.<span class="hl-fn">all</span>(attachmentPromises).<span class="hl-fn">then</span> { (attachments) -&gt; <span class="hl-type">Promise</span>&lt;<span class="hl-type">Post</span>&gt; <span class="hl-kw">in</span>
	<span class="hl-type">ApiClient</span>.<span class="hl-prop">shared</span>.<span class="hl-fn">createPost</span>(text: <span class="hl-kw">self</span>.<span class="hl-prop">postText</span>, attachments: attachments)
}.<span class="hl-fn">then</span> { (post) -&gt; <span class="hl-type">Post</span> <span class="hl-kw">in</span>
	<span class="hl-type">ApiObjectCache</span>.<span class="hl-prop">shared</span>.<span class="hl-fn">store</span>(post)

	<span class="hl-kw">self</span>.<span class="hl-prop">currentDraft</span>?.<span class="hl-fn">remove</span>()

	<span class="hl-kw">return</span> post
}.<span class="hl-fn">handle</span>(on: <span class="hl-type">DispatchQueue</span>.<span class="hl-prop">main</span>).<span class="hl-fn">then</span> { (post) <span class="hl-kw">in
	self</span>.<span class="hl-fn">dimiss</span>(animated: <span class="hl-kw">true</span>)
}.<span class="hl-kw">catch</span> { (error) -&gt; <span class="hl-type">Void</span> <span class="hl-kw">in
	let</span> alert = <span class="hl-fn">createAlertController</span>(title: <span class="hl-str">"Couldn't Post"</span>, message: error.<span class="hl-prop">localizedDescription</span>)
	<span class="hl-kw">self</span>.<span class="hl-fn">present</span>(alert, animated: <span class="hl-kw">true</span>)
}
</code></pre>
<p>As for my own purposes, I don’t know whether I’ll end up using this or not. It’s neat, but it feels like it’s verging on an unnecessary abstraction. Either way, it was a fun experiment.</p>
<p>If you want to check out the full code, the project is in <a href="https://git.shadowfacts.net/shadowfacts/SimpleSwiftPromises" data-link="git.shadowfacts.net/shadowfacts/SimpleSw…">a repo</a> on my Gitea (trying to do anything asynchronous in a Swift Playground is painful). I’ve also made public a <a href="https://git.shadowfacts.net/shadowfacts/Tusker/src/branch/simple-swift-promises" data-link="git.shadowfacts.net/shadowfacts/Tusker/s…">branch</a> of Tusker which is using these promises in some places. </p>
]]></content:encoded>
    </item>
    <item>
      <title>Faking the Mongo Eval Command</title>
      <link>https://shadowfacts.net/2020/faking-mongo-eval/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2020/faking-mongo-eval/</guid>
      <pubDate>Tue, 28 Jan 2020 23:33:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>One of the changes in MongoDB 4.2 was the removal of the <code>eval</code> command. While a reasonable security measure, this is rather annoying if you’re building <a href="https://git.shadowfacts.net/shadowfacts/MongoView" data-link="git.shadowfacts.net/shadowfacts/MongoVie…">an app</a> for interacting directly with a Mongo database. If you want to be able to run commands directly on the database, you now have to go through the <code>mongo</code> shell. This seems straightforward, but actually getting the data back into a format that’s usable is a bit of a hassle.</p>
<!-- excerpt-end -->
<p>Actually running the command is, surprisingly, the easiest part of this whole endeavor. You can simply launch a <a href="https://developer.apple.com/documentation/foundation/process" data-link="developer.apple.com/documentation/founda…"><code>Process</code></a> which invokes the <code>mongo</code> shell with a few options as well the command to evaluate:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> mongoProc = <span class="hl-type">Process</span>()
process.<span class="hl-prop">launchPath</span> = <span class="hl-str">"/usr/local/bin/mongo"</span>
mongoProc.<span class="hl-prop">arguments</span> = [<span class="hl-str">"mongodb://localhost:27017/your_database"</span>, <span class="hl-str">"--quiet"</span>, <span class="hl-str">"--norc"</span>, <span class="hl-str">"--eval"</span>, command]
mongoProc.<span class="hl-fn">launch</span>()
</code></pre>
<p>The <code>--quiet</code> option prevents the shell from logging its own messages, making parsing the output a little easier. The <code>--norc</code> option prevents it from executing <code>.monorc.js</code> on startup, so that the environment our command is running in is entirely standard. The <code>--eval</code> option does exactly what it says, it evaluates the following parameter in the shell.</p>
<p>This bit of code does make the assumption that the mongo shell is installed in or linked to <code>/usr/local/bin/mongo</code> (i.e., it’s been installed through Homebrew). To do this properly, you would probably want to try and detect where Mongo is installed and use that path, as well as offer the user a way of customizing the path.</p>
<p>One additional thing to note is that launching an arbitrary executable requires either the App Sandbox be disabled, or Full Disk Access be requested (at least on macOS Catalina), otherwise the process will fail to launch with a message saying “launch path not accessible”.</p>
<p>Getting the output is a little bit more difficult, but still not too complicated.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> outputPipe = <span class="hl-type">Pipe</span>()

<span class="hl-kw">let</span> mongoProc = <span class="hl-type">Process</span>()
<span class="hl-cmt">// ...</span>
mongoProc.<span class="hl-prop">standardOutput</span> = outputPipe
mongoProc.<span class="hl-fn">launch</span>()

<span class="hl-kw">let</span> outputHandle = outputPipe.<span class="hl-prop">fileHandleForReading</span>

<span class="hl-kw">var</span> output = <span class="hl-str">""</span>
<span class="hl-kw">var</span> data: <span class="hl-type">Data</span>!
<span class="hl-kw">do</span> {
	data = outputHandle.<span class="hl-prop">availableData</span>
	output.<span class="hl-fn">append</span>(<span class="hl-type">String</span>(data: data, encoding: .<span class="hl-prop">utf8</span>))
} <span class="hl-kw">while</span> (data.<span class="hl-prop">count</span> &gt; <span class="hl-num">0</span>)

outputHandle.<span class="hl-fn">closeFile</span>()
</code></pre>
<p>We can create a <a href="https://developer.apple.com/documentation/foundation/pipe" data-link="developer.apple.com/documentation/founda…"><code>Pipe</code></a> object representing a UNIX pipe. The <code>mongo</code> process then uses that pipe as its stdout. We can then read from the pipe’s output file handle in order to get the contents of what the shell printed.</p>
<p>Many StackOverflow posts on the topic of getting the output from a process just call <code>waitUntilExit</code> on the process and then read the entirety of the data from the pipe’s output file handle. While suitable for small output, this approach does not work for situations where the total output of the command is greater than the buffer size of the pipe (as may very well be the case when running queries against large databases). To solve this, we need to continuously read from the pipe until there’s no remaining data (meaning the pipe has closed).</p>
<p>Now that we’ve got the output from Mongo, we need to get it into Swift. Unfortunately, parsing it is a bit annoying. The <code>mongo</code> shell outputs a non-standardized format that’s like JSON, but with a bunch of JavaScript helpers (e.g. <code>ObjectId(&quot;5e00eb48a14888e105a74fda&quot;)</code>) embedded in it. The <a href="https://github.com/mongodb/mongo-swift-driver" data-link="github.com/mongodb/mongo-swift-driver">MongoSwift</a> library can’t parse this format (nor can anything else, as far as I can tell). So, in order to turn the shell output into the <a href="https://docs.mongodb.com/manual/reference/mongodb-extended-json/" data-link="docs.mongodb.com/manual/reference/mongod…">Extended JSON</a> format that MongoSwift can parse, we’ll need to modify the command that we invoke the shell with.</p>
<p>We’ll add some helper code at the beginning of the command we send that defines a function on both the <code>Object</code> and <code>Array</code> prototypes. This function will take whatever it’s invoked on, pass it through <code>JSON.stringify</code> to convert it to Extended JSON, and then print it to the console.</p>
<p>The same function defined on the <code>Array</code> prototype will perform the same operations, just for each operation in the array, instead of on the array object as a whole. This isn’t strictly necessary, but for my purposes I don’t want to deal with top-level arrays, and this will make handling it a bit simpler as top-level array elements will be newline-delimited.</p>
<pre class="highlight" data-lang="javascript"><code>Object.<span class="hl-prop">prototype</span>.<span class="hl-fn">printExtJSON</span> <span class="hl-op">=</span> <span class="hl-kw">function</span>() { <span class="hl-fn">print</span>(<span class="hl-const">JSON</span>.<span class="hl-fn">stringify</span>(<span class="hl-builtin">this</span>)); };
Array.<span class="hl-prop">prototype</span>.<span class="hl-fn">printExtJSON</span> <span class="hl-op">=</span> <span class="hl-kw">function</span>() { <span class="hl-builtin">this</span>.<span class="hl-fn">map</span>(<span class="hl-const">JSON</span>.<span class="hl-prop">stringify</span>).<span class="hl-fn">forEach</span>(<span class="hl-var">it</span> <span class="hl-op">=&gt;</span> <span class="hl-fn">print</span>(<span class="hl-var">it</span>)); };
</code></pre>
<p>For the Array helper, we can’t just call <code>.forEach(print)</code> since <code>forEach</code> passes in multiple arguments (the value, the current index, and the whole array) all of which would get printed out if passed directly to <code>print</code>.</p>
<p>We can include these helpers at the beginning of our command and call it on the expression we’ve been passed in (where <code>prelude</code> is a string containing the above JavaScript code):</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> command = <span class="hl-str">"</span>\(prelude)\(command)<span class="hl-str">.printExtJSON()"</span>
</code></pre>
<p>This approach does have a drawback: only the result of the last expression in the user-inputted <code>command</code> will be stringified and printed. The results of any statements before will be lost, unless the command specifically calls our <code>printExtJSON</code> helper. Again, for my purposes, this is a reasonable trade off.</p>
<p>Now, back to Swift. We’ve got the Extended JSON output from the Mongo shell as one giant string and we just need to have MongoSwift parse it into something usable. Because of the way we’re printing arrays (separate <code>print()</code> calls for each element) and the fact that we’ve put everything through <code>JSON.stringify</code>, it is guaranteed that there will be only one document per line of output. So, to parse each document separately, we can simply split the output we got at newlines and parse each individually:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">let</span> decoder = <span class="hl-type">BSONDecoder</span>()
<span class="hl-kw">let</span> result = output.<span class="hl-fn">components</span>(separatedBy: <span class="hl-str">"\n"</span>).<span class="hl-fn">compactMap</span> { (json) <span class="hl-kw">in
  try</span>? decoder.<span class="hl-fn">decode</span>(<span class="hl-type">BSON</span>.<span class="hl-kw">self</span>, from: json)
}
</code></pre>
<p>And there we have it, the data is finally in a form we can understand from the Swift side of things. If you want to see the whole source code for this, it’s <a href="https://git.shadowfacts.net/shadowfacts/MongoView/src/commit/9488c108b693607e827ef77e5bc16f2cdd491f7c/MongoView/MongoEvaluator.swift" data-link="git.shadowfacts.net/shadowfacts/MongoVie…">part of MongoView</a>. As far as I have come up with, that’s about the best way of replicating the <code>eval</code> command of previous versions of Mongo. If you have any suggestions for how to improve this or make it more robust, let me know!</p>
]]></content:encoded>
    </item>
    <item>
      <title>Mocking HTTP Requests for iOS App UI Tests</title>
      <link>https://shadowfacts.net/2019/mock-http-ios-ui-testing/</link>
      <category>swift</category>
      <guid>https://shadowfacts.net/2019/mock-http-ios-ui-testing/</guid>
      <pubDate>Sun, 22 Dec 2019 23:12:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>I recently decided to start writing User Interface tests for <a href="https://git.shadowfacts.net/shadowfacts/Tusker" data-link="git.shadowfacts.net/shadowfacts/Tusker">Tusker</a>, my iOS app for Mastodon and Pleroma. But I couldn’t just write tests that interacted with an account on any real instance, as that would be far too unpredictable and mean my tests could have an impact on other people. The solution to this problem is, of course, mocking. The core idea is that instead of interacting with external things, your program interacts with mock versions of them, which appear to be their real counterparts, but don’t actually perform any of the operations they claim to. This allows for very tight control over what data the application receives, making it much more amenable to testing.</p>
<p>Unfortunately, if you search around, some of the most common solutions on the internet recommend using the environment variables (one of the only ways of sending data directly from the test to the application under test) to insert the mocked response into the app. Meaning, for every API response you need to mock, you would have an environment variable that contains the response data. This isn’t a great solution, because it leaves whole code paths untested (everything after the request URL would be generated). It would also mean that there’s no way of testing things like what requests are actually made by your app.</p>
<p>The solution to this problem is to actually run a local HTTP server that functions as the API server. This way, the app can communicate with the web server exactly as it would in the normal operating environment, but still have strictly controlled data. Of course, actually doing this isn’t quite so straightforward.</p>
<!-- excerpt-end -->
<p>There are a couple of things to think about when looking for a solution: First, in order to meet the requirement of being able to test what API calls are made, the web server needs to be accessible to the test process. Second, we want to avoid modifications to the app if at all possible. There should be as few differences as possible in the app between the testing and production environments. The more differences, the more code that goes untested and the more potential edge-cases.</p>
<p>For the first requirement, there are a pair of handy open-source libraries that we can use to take care of the grunt work of responding to HTTP requests and serving responses. First there’s <a href="https://github.com/envoy/Embassy" data-link="github.com/envoy/Embassy">Embassy</a>, which acts as an asynchronous HTTP server. It handles actually listening for connections on a port and receiving and sending data to them. The other part of this stack is <a href="https://github.com/envoy/Ambassador" data-link="github.com/envoy/Ambassador">Ambassador</a> which handles routing incoming requests and responding to them in a nicer fashion than just sending strings out.</p>
<p>(Note: both of these libraries are made by a company called Envoy, to which I have no relation.)</p>
<p>So, we need to get these libraries into our project, with the condition that we only want them to be present at test-time, not for release builds (as that would mean shipping unnecessary, extra code). If you’re using CocoaPods, it can take care of this for you. But if you’re not using a package manager, it’s (unsurprisingly) more complicated.</p>
<p>First, the Xcode projects for the two libraries need to be included into the workspace for your app<sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. Next, our app needs to be configured to actually compile against the two frameworks so we can use them in our tests. This is done in the app target of the Xcode project. Both frameworks should be added to the “Frameworks, Libraries, and Embedded Content” section of the General tab for the app target. Once added, the libraries should be set as “Do Not Embed”. We don’t want Xcode to embed the frameworks, because we’ll handle that ourself, conditioned on the current configuration.</p>
<p>To handle that, add a new Run Script phase to the Build Phases section of the target. It’ll need input and output files configured for each of our projects:</p>
<p>Input files:</p>
<ul>
<li><code>${BUILT_PRODUCTS_DIR}/Embassy.framework</code></li>
<li><code>${BUILT_PRODUCTS_DIR}/Ambassador.framework</code></li>
</ul>
<p>Output files:</p>
<ul>
<li><code>${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Embassy.framework</code></li>
<li><code>${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Ambassador.framework</code></li>
</ul>
<p>For reference, the <code>BUILT_PRODUCTS_DIR</code> environment variable refers to the location in Xcode’s DerivedData folder where, as the name suggests, the compiled outputs of the project’s targets live. <code>FRAMEWORKS_FOLDER_PATH</code> refers to the Frameworks folder inside of our app bundle (e.g. <code>Tusker.app/Frameworks</code>).</p>
<p>Configuring input and output files for the Run Script build phase instead of just hard-coding paths in the script itself has the advantage that Xcode will know to re-run our script if the input files change or the output files are missing.</p>
<p>The script our build phase will run is the following:</p>
<pre class="highlight" data-lang="bash"><code><span class="hl-kw">if</span> [ <span class="hl-str">&quot;<span class="hl-emb">${<span class="hl-prop">CONFIGURATION</span>}</span>&quot;</span> == <span class="hl-str">&quot;Debug&quot;</span> ]; <span class="hl-kw">then</span>
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;Embedding <span class="hl-emb">${<span class="hl-prop">SCRIPT_INPUT_FILE_0</span>}</span>&quot;</span>
    <span class="hl-fn">cp</span> <span class="hl-const">-R</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_INPUT_FILE_0</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_OUTPUT_FILE_0</span>
    <span class="hl-fn">codesign</span> <span class="hl-const">--force</span> <span class="hl-const">--verbose</span> <span class="hl-const">--sign</span> <span class="hl-op">$</span><span class="hl-prop">EXPANDED_CODE_SIGN_IDENTITY</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_OUTPUT_FILE_0</span>
    
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;Embedding <span class="hl-emb">${<span class="hl-prop">SCRIPT_INPUT_FILE_1</span>}</span>&quot;</span>
    <span class="hl-fn">cp</span> <span class="hl-const">-R</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_INPUT_FILE_1</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_OUTPUT_FILE_1</span>
    <span class="hl-fn">codesign</span> <span class="hl-const">--force</span> <span class="hl-const">--verbose</span> <span class="hl-const">--sign</span> <span class="hl-op">$</span><span class="hl-prop">EXPANDED_CODE_SIGN_IDENTITY</span> <span class="hl-op">$</span><span class="hl-prop">SCRIPT_OUTPUT_FILE_1</span>
<span class="hl-kw">else</span>
    <span class="hl-fn">echo</span> <span class="hl-str">&quot;Skipping embedding debug frameworks&quot;</span>
<span class="hl-kw">fi</span>
</code></pre>
<p>If the product configuration is anything other than Debug, the script will simply log a message and do nothing else. If it is in Debug configuration, then we’ll take a couple actions for each framework: First, we simply copy the built framework into the app’s Frameworks folder. Then, we re-codesign the frameworks in the app so that they’re signed with the same identity as our app is. In the script, the input/output file environment variables refer to exactly what they say, the input and output files configured for the build phase in Xcode. The <code>EXPANDED_CODE_SIGN_IDENTITY</code> environment variable gives the code signing identity<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup> that was used to sign the app, which is the same one we want to sign the frameworks with. We also need to give codesign the <code>--force</code> option, so that it will overwrite any existing signature on the framework.</p>
<p>Now, if we build our app for debugging and take a look at the frameworks folder inside the app bundle, we can see both <code>Embassy.framework</code> and <code>Ambassador.framework</code> are present. Switching to the release build and again looking at the product in Finder, neither of those two frameworks are present.</p>
<p>One thing to be aware of is that this setup only excludes the frameworks from being <em>copied</em> in release configurations. They’ll still be available at compile-time, so if you’re not paying attention, you could accidentally import one of them and start using it, only to encounter a crash in production due to a missing framework. As far as I could tell, there’s no way within Xcode of specifying that a framework only be compiled against in certain configurations. If I’ve missed something and this is indeed possible, please let me know.</p>
<p>With that finally done, we can start setting up the web server so we can test our app. This simplest way to do this is to create a base class for all of our test cases which inherits from <code>XCTestCase</code> that will handling setting up the web server. We’ll have a method called <code>setUpWebServer</code> which is called from <code>XCTestCase</code>’s <code>setUp</code> method. (In our actual test case, we’ll need to be sure to call <code>super.setUp()</code> if we override the <code>setUp</code> method.)</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">var</span> eventLoop: <span class="hl-type">EventLoop</span>!
<span class="hl-kw">var</span> router: <span class="hl-type">Router</span>!
<span class="hl-kw">var</span> eventLoopThreadCondition: <span class="hl-type">NSCondition</span>!
<span class="hl-kw">var</span> eventLoopThread: <span class="hl-type">Thread</span>!

<span class="hl-kw">private func</span> setUpWebServer() {
	eventLoop = <span class="hl-kw">try</span>! <span class="hl-type">SelectorEventLoop</span>(selector: <span class="hl-kw">try</span>! <span class="hl-type">KqueueSelector</span>())
	router = <span class="hl-type">Router</span>()
	server = <span class="hl-type">DefaultHTTPServer</span>(eventLoop: eventLoop, port: <span class="hl-num">8080</span>, app: router.<span class="hl-prop">app</span>)
	<span class="hl-kw">try</span>! server.<span class="hl-fn">start</span>()

	<span class="hl-fn">eventLoopThreadCondition</span>() = <span class="hl-type">NSCondition</span>()
	eventLoopThread = <span class="hl-type">Thread</span>(block: {
		<span class="hl-kw">self</span>.<span class="hl-prop">eventLoop</span>.<span class="hl-fn">runForever</span>()
		<span class="hl-kw">self</span>.<span class="hl-prop">eventLoopThreadCondition</span>.<span class="hl-fn">lock</span>()
		<span class="hl-kw">self</span>.<span class="hl-prop">eventLoopThreadCondition</span>.<span class="hl-fn">signal</span>()
		<span class="hl-kw">self</span>.<span class="hl-prop">eventLoopThreadCondition</span>.<span class="hl-fn">unlock</span>()
	})
	eventLoopThread.<span class="hl-fn">start</span>()
}
</code></pre>
<p>In that method, we’ll create an event loop which handles performing asynchronous operations for the web server. We’ll also need a <code>Router</code> object which will handle HTTP requests and delegate them to other handlers based on the request’s path. Next, we’ll need the web server itself, an instance of <code>DefaultHTTPServer</code>. Once we’ve got that, we can add our routes to the router and start the web server. Finally, we’ll create a separate thread that runs in the background for actually processing web requests. We also override the <code>tearDown</code> method and stop the web server, waiting for it to gracefully shut down.</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">override func</span> tearDown() {
	server.<span class="hl-fn">stopAndWait</span>()
	eventLoopThreadCondition.<span class="hl-fn">lock</span>()
	eventLoop.<span class="hl-fn">stop</span>()
	<span class="hl-kw">while</span> eventLoop.<span class="hl-fn">running</span> {
		<span class="hl-kw">if</span> !eventLoopThreadCondition.<span class="hl-fn">wait</span>(until: <span class="hl-type">Date</span>(timeIntervalSinceNow: <span class="hl-num">10</span>)) {
			<span class="hl-fn">fatalError</span>(<span class="hl-str">"Join eventLoopThread timeout"</span>)
		}
	}
}
</code></pre>
<p>Now that we’ve finally gotten everything set up, we can test out the web server and make sure everything’s working. In a new test case class, we’ll extend the base class we created instead of <code>XCTestCase</code>, and we’ll override the <code>setUp</code> method to add a new route:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">override func</span> setUp() {
	<span class="hl-kw">super</span>.<span class="hl-fn">setUp</span>()
	router[<span class="hl-str">"/hello"</span>] = <span class="hl-type">JSONResponse</span>(handler: { (<span class="hl-kw">_</span>) <span class="hl-kw">in
		return</span> [<span class="hl-str">"Hello"</span>: <span class="hl-str">"World"</span>]
	})
}
</code></pre>
<p>To actually test it out, a simple test method that just sleeps for a long time will do the trick:</p>
<pre class="highlight" data-lang="swift"><code><span class="hl-kw">func</span> testWebServer() {
	<span class="hl-fn">sleep</span>(<span class="hl-num">10000000</span>)
}
</code></pre>
<p>Once we run the test and the simulator’s up and runing, we can visit <code>http://localhost:8080/hello</code> in a web browser and see the JSON response we defined. Now, actually using the mock web server from the app is a simple matter of adding an environment variable the override the default API host.</p>
<p>One caveat to note with this set up is that, because the web server is running in the same process as the test code (just in a different thread), when the debugger pauses in a test (<em>not</em> in the app itself), any web requests we make to the mock server won’t complete until the process is resumed.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>I tried to use Xcode 11’s new Swift Package Manager support for this, but as far as I can tell, it doesn’t provide direct access to the built frameworks produced by the libraries, so this technique isn’t possible. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>“Expanded” refers to the format of the identifier. If you look at the <code>codesign</code> manpage, it describes several formats that can be used with the <code>--sign</code> operation. The expanded format is the forty hexadecimal digit SHA-1 hash of the identity. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Building a JavaScript-Free Slide-Over Menu</title>
      <link>https://shadowfacts.net/2019/js-free-hamburger-menu/</link>
      <category>web</category>
      <guid>https://shadowfacts.net/2019/js-free-hamburger-menu/</guid>
      <pubDate>Tue, 12 Nov 2019 01:08:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>Slide-over menus on the web are a pretty common design pattern, especially on mobile. Unfortunately, they seem to generally be accompanied by massive, bloated web apps pulling in megabytes of JavaScript for the simplest of functionality. But fear not, even if you’re building a JavaScript-free web app, or simply prefer to fail gracefully in the event the user has disabled JavaScript, it’s still possible to use this technique by (ab)using HTML form and label elements.</p>
<!-- excerpt-end -->
<p>Now, I could just spew a bunch of code onto the page and give you cursory explanation of what it’s doing, but that would be boring, so instead I’m going to walk through the progression of how I built it and the reasons for the changes I made along the way. If that’s all you want, you can take a look at the <a href="/2019/js-free-hamburger-menu/final.html" data-link="final.html">final version</a> and View Source to see all the code.</p>
<p>We’ll start off with a tiny bit of markup in the body (I’m assuming you can set up an HTML page yourself):</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-content</span>&quot;&gt;
	&lt;<span class="hl-tag">p</span>&gt;Some sidebar content&lt;/<span class="hl-tag">p</span>&gt;
&lt;/<span class="hl-tag">div</span>&gt;
&lt;<span class="hl-tag">main</span>&gt;
	&lt;<span class="hl-tag">p</span>&gt;
		<span class="hl-cmt">&lt;!-- lorem ipsum text, just so we don't have an entirely blank page --&gt;</span>
	&lt;/<span class="hl-tag">p</span>&gt;
&lt;/<span class="hl-tag">main</span>&gt;
</code></pre>
<p>We’ll also have some basic CSS to start, so looking at our page isn’t looking quite so painful.</p>
<pre class="highlight" data-lang="css"><code><span class="hl-tag">body</span> {
	<span class="hl-prop">font-family</span>: sans-serif;
}

<span class="hl-tag">main</span> {
	<span class="hl-prop">position</span>: relative;
	<span class="hl-prop">max-width</span>: <span class="hl-num">980<span class="hl-type">px</span></span>;
	<span class="hl-prop">margin</span>: <span class="hl-num">0</span> auto;
}
</code></pre>
<p>Then, we’ll need the element this whole thing hinges on: a checkbox input. Because of the CSS trick we’re using to implement the visibility toggling, the checkbox element needs to be at the same level in the DOM as our <code>#sidebar-container</code> element. We’re going to use the adjacent sibling selector (<code>+</code>), which means that the checkbox input needs to come directly before the sidebar container, but you could also use the general sibling selector (<code>~</code>) which would let you put the checkbox anywhere in the DOM given it has the same parent element as the sidebar container.</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">input</span> <span class="hl-attr">type</span>=&quot;<span class="hl-str">checkbox</span>&quot; <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-visible</span>&quot;&gt;
&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-content</span>&quot;&gt;
<span class="hl-cmt">&lt;!-- ... --&gt;</span>
</code></pre>
<p>The other half of the HTML behavior that we’re relying on to make this work without JavaScript is that clicking <code>&lt;label&gt;</code> tags that are associated with a checkbox toggles the checkbox, and that the label tag can be anywhere in the document in relation to their associated element. We can also have several label elements controlling the same checkbox, which will let us provide a couple different options to the user for how to close the slide-over menu.</p>
<p>We’ll need a couple of labels to start with: one shown next to the content and one that will be inside the menu to dismiss it.</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-content</span>&quot;&gt;
	&lt;<span class="hl-tag">p</span>&gt;Some sidebar content&lt;/<span class="hl-tag">p</span>&gt;
	&lt;<span class="hl-tag">label</span> <span class="hl-attr">for</span>=&quot;<span class="hl-str">sidebar-visible</span>&quot; <span class="hl-attr">class</span>=&quot;<span class="hl-str">sidebar-toggle</span>&quot;&gt;Close&lt;/<span class="hl-tag">label</span>&gt;
&lt;/<span class="hl-tag">div</span>&gt;
&lt;<span class="hl-tag">main</span>&gt;
	&lt;<span class="hl-tag">label</span> <span class="hl-attr">for</span>=&quot;<span class="hl-str">sidebar-visible</span>&quot; <span class="hl-attr">class</span>=&quot;<span class="hl-str">sidebar-toggle</span>&quot;&gt;Open Sidebar&lt;/<span class="hl-tag">label</span>&gt;
	<span class="hl-cmt">&lt;!-- ... --&gt;</span>
&lt;/<span class="hl-tag">main</span>&gt;
</code></pre>
<p>Now, all we need to start toggling our sidebar is just a few CSS rules:</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-visible</span> {
	<span class="hl-prop">display</span>: none;
}
.<span class="hl-tag">sidebar-toggle</span> {
	<span class="hl-prop">cursor</span>: pointer;
}
#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">display</span>: none;
}
#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">display</span>: block;
}
</code></pre>
<p>The user never needs to see the checkbox itself, since they’ll always interact with it through the label elements, so we can always hide it. For a good measure, we’ll have our labels use the pointer cursor when they’re hovered over, to hint to the user that they can be clicked on. Then we’ll hide the sidebar content element by default, since we want it to start out hidden.</p>
<p>The most important rule, and what this whole thing hinges on, is that last selector. We’re looking for an element with the ID <code>sidebar-visible</code> that matches the <code>:checked</code> pseudo-selector (which only applies to checked checkboxes or radio inputs) that <em>has a sibling</em> whose ID is <code>sidebar-content</code>. The key is that the element we’re actually selecting here is the <code>#sidebar-content</code>, not the checkbox itself. We’re essentially using the <code>:checked</code> pseudo-selector as a predicate, telling the browser that we only want to select the sidebar content element <em>when our checkbox is checked</em>. </p>
<p>If we take a look at <a href="/2019/js-free-hamburger-menu/toggling.html" data-link="toggling.html">our web page now</a>, we can see we’ve got the building blocks in place for our slide-over menu. The page starts off not showing our sidebar content, but we can click the Open Sidebar label to show it, and then click the Close label to hide it once more.</p>
<p>Next, we’ll need a bit of CSS to get it looking more like an actual sidebar. To start off, we’ll give it a fixed position with all of its edges pinned to the edges of the viewport. We’ll also give it a nice high z-index, to make sure it’s shown above all of the regular content on our page.</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">display</span>: none;
	<span class="hl-prop">position</span>: fixed;
	<span class="hl-prop">top</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">bottom</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">left</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">right</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">z-index</span>: <span class="hl-num">100</span>;
}
</code></pre>
<p>This will get our sidebar element positioned correctly, but it’s not so pretty. To clean it up a bit, we’ll move the sidebar content element inside a new container element. Giving both elements background colors will also provide a visual cue of where the sidebar is in relation to the main content of our page.</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-container</span>&quot;&gt;
	&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-content</span>&quot;&gt;
		&lt;<span class="hl-tag">label</span> <span class="hl-attr">for</span>=&quot;<span class="hl-str">sidebar-visible</span>&quot; <span class="hl-attr">class</span>=&quot;<span class="hl-str">sidebar-toggle</span>&quot;&gt;Close&lt;/<span class="hl-tag">label</span>&gt;
		&lt;<span class="hl-tag">p</span>&gt;Some sidebar content&lt;/<span class="hl-tag">p</span>&gt;
	&lt;/<span class="hl-tag">div</span>&gt;
&lt;/<span class="hl-tag">div</span>&gt;
</code></pre><pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">display</span>: none;
	<span class="hl-prop">position</span>: fixed;
	<span class="hl-prop">top</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">bottom</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">left</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">right</span>: <span class="hl-num">0</span>;
	<span class="hl-prop">z-index</span>: <span class="hl-num">100</span>;
	<span class="hl-prop">background-color</span>: <span class="hl-fn">rgba</span>(<span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-num">0.3</span>);
}
#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">display</span>: block;
}
#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">background-color</span>: <span class="hl-str">#eee</span>;
}
</code></pre>
<p>Note that we’ve change the rules that we previously applied to <code>#sidebar-content</code> to now target the <code>#sidebar-container</code> element, since that’s now the root element of our sidebar. If we take a look at our page again, now we’ll see that displaying the content works correctly and the backgrounds for the various parts of our page are good. But, the sidebar looks more like a topbar. Let’s fix that by giving it an explicit size, instead of letting it size itself:</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">width</span>: <span class="hl-num">25<span class="hl-type">%</span></span>;
	<span class="hl-prop">height</span>: <span class="hl-num">100<span class="hl-type">vh</span></span>;
}
</code></pre>
<p>If you haven’t encountered it before the <code>vh</code> unit in CSS represents a percentage of the viewport’s height, so <code>100vh</code> is the height of the viewport and <code>50vh</code> would be half the height of the viewport (likewise, there’s a <code>vw</code> unit representing the viewport’s width).</p>
<p><a href="/2019/js-free-hamburger-menu/background.html" data-link="background.html">Now</a> we’re making good progress. Trying out our slide-over menu, one thing that would be nice is the ability to click anywhere <em>outside</em> of the menu to dismiss it, as if we had clicked close instead. We can accomplish that by adding yet another label that’s hooked up to our checkbox:</p>
<pre class="highlight" data-lang="html"><code>&lt;<span class="hl-tag">div</span> <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-container</span>&quot;&gt;
	<span class="hl-cmt">&lt;!-- ... --&gt;</span>
	&lt;<span class="hl-tag">label</span> <span class="hl-attr">for</span>=&quot;<span class="hl-str">sidebar-visible</span>&quot; <span class="hl-attr">id</span>=&quot;<span class="hl-str">sidebar-dismiss</span>&quot;&gt;&lt;/<span class="hl-tag">label</span>&gt;
&lt;/<span class="hl-tag">div</span>&gt;
</code></pre>
<p>We’ll need to position and size this label so that it covers the entire rest of the page. We could do this manually, specifying the position and sizes of each label, or we could be a bit clever and use Flexbox. First, we’ll need to go back and change our sidebar container to be in flexbox mode when it’s shown:</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">display</span>: flex;
	<span class="hl-prop">flex-direction</span>: row;
}
</code></pre>
<p>We set the flex direction to row because our sidebar content and the label will layout horizontally across the page, with our content on the left and the dismissal label on the right. We can also go back to our sidebar content styles and remove the height rule. Since we don’t specify otherwise, the flex items will expand to fill the container along the axis perpendicular to the flex direction (in this case, that will be the vertical axis), and since the flex container fills the viewport height, so too will the flex items.</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">background-color</span>: <span class="hl-str">#eee</span>;
	<span class="hl-prop">width</span>: <span class="hl-num">25<span class="hl-type">%</span></span>;
}
</code></pre>
<p>Making our dismissal label fill the remaining space is then as simple as setting its flex-grow priority to 1 (any number greater than the default of 0, which our content has, will do).</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-dismiss</span> {
	<span class="hl-prop">flex-grow</span>: <span class="hl-num">1</span>;
}
</code></pre>
<p>On <a href="/2019/js-free-hamburger-menu/dismiss.html" data-link="dismiss.html">our updated page</a>, after opening the slide-over menu, we can click anywhere outside it (in the translucent, darkened area) to dismiss the menu.</p>
<p>The last thing that would be nice to add is a simple transition for when the menu opens or closes. Before we can start adding transitions, we’ll need to make a change to our existing CSS. Currently, we’re hiding and showing the menu using the display property, switching between none and flex. But that won’t work with transitions. Since the browser has no way of knowing how we want to interpolate between the two values, none of the transitions we specify will have any effect because the container will still be shown/hidden instantaneously. Luckily, there’s another solution to hiding and showing the container element: the <code>visibility</code> property, which <em>is</em> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/visibility#Interpolation" data-link="developer.mozilla.org/en-US/docs/Web/CSS…">interpolatable</a> between <code>visible</code> and <code>hidden</code>. So, we’ll change our container to always be in flexbox mode, but start out being hidden and then become visible when the checkbox is toggled on.</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">visibility</span>: hidden;
	<span class="hl-prop">display</span>: flex;
	<span class="hl-prop">flex-direction</span>: row;
	<span class="hl-cmt">/* ... */</span>
}
#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">visibility</span>: visible;
}
</code></pre>
<p>Now we’ve got the exact same behavior as before, but we have the ability to add transitions. Let’s start with making the partially opaque background to fade in and out as the menu is shown and hidden. We can accomplish this by moving the background color rule to only apply when the checkbox is checked, and have the container start out with a fully transparent background color. We’ll also instruct it to transition both the <code>visibility</code> and <code>background-color</code> properties.</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-container</span> {
    <span class="hl-cmt">/* ... */</span>
    <span class="hl-prop">background-color</span>: transparent;
    <span class="hl-prop">transition</span>:
          visibility <span class="hl-num">0.35<span class="hl-type">s</span></span> ease-in-out,
          background-color <span class="hl-num">0.35<span class="hl-type">s</span></span> ease-in-out;
}
#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-container</span> {
	<span class="hl-prop">visibility</span>: visible;
	<span class="hl-prop">background-color</span>: <span class="hl-fn">rgba</span>(<span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-num">0</span>, <span class="hl-num">0.3</span>);
}
</code></pre>
<p>Now if we try showing and hiding the menu, we can see the semi-translucent gray background fades in and out properly, but the sidebar content itself is still shwoing up immediately, without any transition. Let’s actually provide a transition for it now. We’ll have it slide on and off the side of the page. To do this, we’ll initially set the sidebar’s left position to <code>-100%</code>, which will put the right edge of the sidebar at the left edge of the screen, leaving the content off-screen. We also need to specify that the content element is positioned relative to itself.</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-cmt">/* ... */</span>
	<span class="hl-prop">position</span>: relative;
	<span class="hl-prop">left</span>: <span class="hl-num">-100<span class="hl-type">%</span></span>;
}
</code></pre>
<p>Then, when the checkbox is checked, we can reset the left property to 0, bringing it back on-screen:</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-visible</span>:<span class="hl-attr">checked</span> <span class="hl-op">+</span> #<span class="hl-tag">sidebar-container</span> <span class="hl-op">&gt;</span> #<span class="hl-tag">sidebar-content</span> {
	<span class="hl-prop">left</span>: <span class="hl-num">0</span>;
}
</code></pre>
<p>Lastly, we’ll tell the sidebar content element to transition its left property with the same parameters as the background transition:</p>
<pre class="highlight" data-lang="css"><code>#<span class="hl-tag">sidebar-content</span> {
	<span class="hl-cmt">/* ... */</span>
	<span class="hl-prop">transition</span>: left <span class="hl-num">0.35<span class="hl-type">s</span></span> ease-in-out;
}
</code></pre>
<p>Now <a href="/2019/js-free-hamburger-menu/transition.html" data-link="transition.html">our menu</a> has a nice transition so it’s not quite so jarring when it’s shown/hidden.</p>
<p>I’ve polished it up a little bit more for the <a href="/2019/js-free-hamburger-menu/final.html" data-link="final.html">final version</a>, but the core of the menu is done! And all without a single line of JavaScript.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Learning Elixir</title>
      <link>https://shadowfacts.net/2019/learning-elixir/</link>
      <category>elixir</category>
      <guid>https://shadowfacts.net/2019/learning-elixir/</guid>
      <pubDate>Thu, 10 Oct 2019 16:29:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>About a year ago, I set out to learn the <a href="https://elixir-lang.org" data-link="elixir-lang.org">Elixir</a> programming language. At the time, it was mainly so I could contribute to <a href="https://pleroma.social" data-link="pleroma.social">Pleroma</a>, but I’ve since fallen in love with the language.</p>
<!-- excerpt-end -->
<p>To actually learn Elixir, I did a few things. I started by reading through the official <a href="https://elixir-lang.org/getting-started/introduction.html" data-link="elixir-lang.org/getting-started/introduc…">Elixir Guide</a>, and after that, following along with the <a href="https://elixirschool.com/en/lessons/basics/basics/" data-link="elixirschool.com/en/lessons/basics/basic…">Elixir School</a> lessons. These were useful in giving me a general idea of how the language works.</p>
<p>I strongly believe that the best way to learn a programming language (especially if you already know others) is to just start writing code. <a href="https://exercism.io/tracks/elixir" data-link="exercism.io/tracks/elixir">Exercism</a> is a resource I’ve found to be quite useful in that process. It has a sequence of programming challenges that increase in difficulty, so it gives you a good feel for what it’s like to use Elixir to actually solve problems and helps shift your brain into thinking about problems in a functional context.</p>
<p>By this point, it was almost December, so I decided I was going to try to do the <a href="https://adventofcode.com" data-link="adventofcode.com">Advent of Code</a> problems only using Elixir. These challenges were more difficult than the Exercism ones, but they provided the same benefit of letting me get experience actually writing Elixir and solving problems with it an isolated context, without a whole bunch of moving parts.</p>
<p>I knew what I ultimately wanted to do with Elixir was build web apps, so after that I went through the official <a href="https://hexdocs.pm/phoenix/overview.html" data-link="hexdocs.pm/phoenix/overview.html">Phoenix Guide</a> which explains the overall architecture of the Phoenix framework and shows you how a bunch of common patterns and techniques for building webapps with it. </p>
<p>Lastly, and most importantly, I actually started building projects using Elixir. The first one I started was <a href="https://git.shadowfacts.net/shadowfacts/frenzy" data-link="git.shadowfacts.net/shadowfacts/frenzy">frenzy</a>, an RSS aggregator I built using Phoenix and Ecto. Originally, the project was a couple hundred lines of shoddily written JS. I wrote it even before I started learning Elixir, inteding it to be a stopgap. As I was learning Elixir, I knew this project was what it was building up to, so as read things and did programming exercises, I noticed things that I thought would become useful once I got around to rewriting frenzy in Elixir. </p>
<p>When learning a language, there’s no substitute for actually learning, and this step was by far the most important for me. In addition to all of the algorithmic experience, and general knowledge of how to write Elixir, actually doing this project gave me the <em>pratical</em> knowledge of what it’s like to actually work with this language and these tools. If you’re interested in learning Elixir (or any programming language, really), my biggest piece of advice is to keep in the back of your head something concrete that you want to build with it.</p>
<p>After having learned Elixir, and continuing to use other languages for other projects, there are few key differences that distinguish Elixir from the other languages I know. They’re not just small differences between other programming languages, They’re what distinguishes between languages that are ‘just another language’ and languages that I find truly enjoyable to use.</p>
<p>Firstly, it is a functional language, but not obtusely so. It’s not steeped in terms and ideas that sound like you need an advanced math degree to understand. Of the functional programming languages that I’ve tried to learn, Elixir has been by far the easiest. It uses FP concepts, but in a way that makes it easy to learn coming from an imperative mindset. There’s no worrying about monads and functors and combinators. The hardest change is learning to use recursion by default instead of instinctively reaching for imperative constructs like loops. You don’t have to think about how specifically to structure your code so that FP concepts fit will with it. You can just write your code in a way that feels normal, and the functional aspects will become second nature.</p>
<p>Secondly, Elixir is <em>fantastic</em> for REPL driven development. It comes with a built-in REPL (<code>iex</code>) which lets you quickly test code fragments, recompile your project, and view documentation.</p>
<p>Like <a href="https://www.erlang.org/" data-link="erlang.org">Erlang</a>, which it’s built on, Elixir runs on the BEAM virtual machine, which provides super-robust code-reloading. During the normal course of development, the only time I ever need to restart the running program is when I change the config file. Other than that, I can make a change or alteration and just run <code>recompile</code> from the <code>iex</code> session and nearly instantly have the all my latest changes running. Even when there are changes to data types or method signatures or compile-time constants, everything can just get swapped in seamlessly. Compared to the hot-swapping in other environments (nowhere near as good on the JVM, non-existent in many more), this is incredible.</p>
<p>The reason I find this makes such a big difference to the way I code is that lets me speed up my internal development loop immensely. I can very rapidly flip back and forth between writing code and testing it; I never feel like I’m being held up by the tools. I can stay in the flow much longer because there are no <a href="https://www.xkcd.com/303/" data-link="xkcd.com/303/">lengthy compile times</a> to let me get distracted or start procrastinating.</p>
<p>Compared to something like iOS app development, this is a godsend. Even in small projects where incremental compiles only take a few seconds, the iteration loop is much slower. My usual development cycle goes something like this: 1) make a change, 2) hit build and run, 3) switch to my browser to glance at social media, 4) 30 seconds later switch to the Simulator and hope it’s finished launching. With Elixir projects, I’m generally just switching back and forth between my editor and the terminal and/or web browser to test whatever I’m working on. There are no intermediate steps. When I make a change, there’s no waiting for an app to launch, or for a database connection to be established, or for a network request to be made, or for config files to be read, or for anything else. Generally, it takes me more time to switch windows and type <code>recompile</code> than it does for the recompilation to actually take place and the change to take effect.</p>
<p>Elixir is a language that I’ve come to find incredibly valuable. It’s very powerful and in the areas where it excels, it’s unique characteristics make it an extremely valuable tool. If you’re thinking about dipping your toes into functional programming, or want to try something new, or even just spend a lot of time doing back end web development,I encourage you to try Elixir.</p>
]]></content:encoded>
    </item>
    <item>
      <title>ActivityPub Resources</title>
      <link>https://shadowfacts.net/2019/activity-pub-resources/</link>
      <category>activitypub</category>
      <guid>https://shadowfacts.net/2019/activity-pub-resources/</guid>
      <pubDate>Sun, 22 Sep 2019 21:50:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>This isn’t really going to be a blog most, but more of a collection of tidbits and resources I found helpful in implenting the <a href="/meta/2019/reincarnation/#activity-pub" data-link="/meta/2019/reincarnation/#activity-pub">ActivityPub integration</a> for the new version of my blog.</p>
<p>This post was last updated on Mar 7, 2023.</p>
<!-- excerpt-end -->
<h3 id="specs"><a href="#specs" class="header-anchor" aria-hidden="true" role="presentation">###</a> Specs</h3>
<ul>
<li>The <a href="https://www.w3.org/TR/activitystreams-core/" data-link="w3.org/TR/activitystreams-core/">ActivityStreams 2.0 spec</a> is important, as it’s what ActivityPub is built on top of.</li>
<li>Similarly, the <a href="https://www.w3.org/TR/activitystreams-vocabulary/" data-link="w3.org/TR/activitystreams-vocabulary/">AS 2.0 Vocabulary</a> defines all the objects and activities that AP actually uses in practice (and many more that it doesn’t).</li>
<li>The <a href="https://www.w3.org/TR/activitypub/" data-link="w3.org/TR/activitypub/">ActivityPub spec</a> itself is quite useful, despite its many omissions.</li>
<li>There’s also <a href="https://litepub.social/litepub/" data-link="litepub.social/litepub/">LitePub</a>, which has some extensions to AP.</li>
<li>The least useful by far spec is <a href="https://www.w3.org/TR/json-ld/" data-link="w3.org/TR/json-ld/">JSON-LD</a> which defines how to use JSON to represent linked data and object graphs. AS2 and AP are both built on this, but if you’re going for a simple implementation (or even a complex one), you can entirely ignore this and treat JSON-LD as plain old JSON objects.</li>
</ul>
<p><a href="https://tinysubversions.com/notes/reading-activitypub/" data-link="tinysubversions.com/notes/reading-activi…">This</a> is also a helpful resource about how to go about reading the AP specification.</p>
<h3 id="actually-federating"><a href="#actually-federating" class="header-anchor" aria-hidden="true" role="presentation">###</a> Actually Federating</h3>
<ul>
<li>Gargron’s blog posts on <a href="https://blog.joinmastodon.org/2018/06/how-to-implement-a-basic-activitypub-server/" data-link="blog.joinmastodon.org/2018/06/how-to-imp…">implementing a basic AP server</a> and <a href="https://blog.joinmastodon.org/2018/07/how-to-make-friends-and-verify-requests/" data-link="blog.joinmastodon.org/2018/07/how-to-mak…">implementing HTTP signatures</a> are good guides for how to actually get federating with other servers in the wild.</li>
<li><a href="https://blog.soykaf.com/post/activity-pub-in-pleroma/" data-link="blog.soykaf.com/post/activity-pub-in-ple…">Lain’s blog post</a> on some of the weird quirks of how ActivityPub actually gets used.</li>
<li><a href="https://blog.dereferenced.org/federation-what-flows-where-and-why" data-link="blog.dereferenced.org/federation-what-fl…">Kaniini’s blog post</a> about how data actually moves through the fediverse.</li>
</ul>
<h3 id="reference-material"><a href="#reference-material" class="header-anchor" aria-hidden="true" role="presentation">###</a> Reference Material</h3>
<ul>
<li>Darius Kazemi has a <a href="https://github.com/dariusk/express-activitypub/" data-link="github.com/dariusk/express-activitypub/">simple reference implementation</a> of an ActivityPub server written using Node.js.</li>
<li>I used the <a href="https://git.pleroma.social/pleroma/pleroma/" data-link="git.pleroma.social/pleroma/pleroma/">Pleroma source code</a> a great deal when working on my implementation, mainly just because I’m familiar with Elixir.</li>
<li>I’d also like to think <a href="https://git.shadowfacts.net/shadowfacts/shadowfacts.net/src/branch/master/lib/activitypub" data-link="git.shadowfacts.net/shadowfacts/shadowfa…">my own implementation</a> is fairly approachable (it’s about 700 lines of not-too-complicated TypeScript).</li>
<li>Ted Unangst has a collection of <a href="https://jawn.tedunangst.com/a/R526ZQ49MbYt5J4KpR" data-link="jawn.tedunangst.com/a/R526ZQ49MbYt5J4KpR">sample data</a> which is useful for comparing how different implementations represent things in AP.</li>
</ul>
<h3 id="libraries"><a href="#libraries" class="header-anchor" aria-hidden="true" role="presentation">###</a> Libraries</h3>
<ul>
<li><a href="https://lib.rs/crates/activitystreams" data-link="lib.rs/crates/activitystreams">activitystreams</a> for serializing/deserializing AS2 types in Rust</li>
<li><a href="https://lib.rs/crates/http-signature-normalization" data-link="lib.rs/crates/http-signature-normalizati…">http-signature-normalization</a> for dealing with part of HTTP signatures in Rust</li>
</ul>
<h3 id="other"><a href="#other" class="header-anchor" aria-hidden="true" role="presentation">###</a> Other</h3>
<ul>
<li>For actually testing federation, <a href="https://ngrok.com/" data-link="ngrok.com">ngrok</a> is very useful for testing your implementations against others. It creates a tunnel from your local machine to a public domain with HTTPS already setup. Because your code is still running locally, you have access to all your usual debugging tools and can iterate rapidly.</li>
<li>Testing against other implementations running locally (be it on your machine or inside a VM/container) lets you access debug logs and see what the other server is actually receiving, which can be quite useful.</li>
<li>Darius Kazemi also wrote <a href="https://tinysubversions.com/notes/activitypub-tool/" data-link="tinysubversions.com/notes/activitypub-to…">an application</a> that lets you send ActivityPub objects directly to other servers, which is useful for testing your application against outside data without polluting other people’s instances.</li>
<li>Ted Unangst also has his own <a href="https://flak.tedunangst.com/post/ActivityPub-as-it-has-been-understood" data-link="flak.tedunangst.com/post/ActivityPub-as-…">compilation of AP-related links</a>.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Reincarnation</title>
      <link>https://shadowfacts.net/2019/reincarnation/</link>
      <category>meta</category>
      <category>activitypub</category>
      <guid>https://shadowfacts.net/2019/reincarnation/</guid>
      <pubDate>Wed, 18 Sep 2019 14:34:42 +0000</pubDate>
      <content:encoded><![CDATA[<figure>
    <img src="/2019/reincarnation/galactic_entity.png" alt="Futurama Galactic Entity image" />
    <figcaption>A wise man once said that nothing really dies, it just comes back in a new form. Then he died.</figcaption>
</figure>
<p>Stand by for <em><strong>reincarnation</strong></em>.</p>
<p>Welcome to the Version Five of my website. Quite a bit has changed, so let’s go over it.</p>
<!-- excerpt-end -->
<h2 id="new-theme"><a href="#new-theme" class="header-anchor" aria-hidden="true" role="presentation">##</a> New Theme</h2>
<p>As you can see (at least, if you’re reading this online), the theme for my website has been redesigned from the ground up. Unlike the <a href="/meta/2016/the-great-redesign/" data-link="/meta/2016/the-great-redesign/">previous version</a>, which was based on someone else’s theme, this design is entirely my own. The design is, in no small part, inspired by <a href="https://www.brutalist-web.design/" data-link="brutalist-web.design">Brutalist Web Design</a><sup class="footnote-reference" id="fnref1"><a href="#1">[1]</a></sup>. It doesn’t look super shiny because it shouldn’t; it is designed, first and foremost, to be pleasant to read. There is a grand total of one (1) fancy animation used, and the overall style is pretty minimal, placing emphasis on the content above all else.</p>
<p>The main decorative choice I made (which is really halfway between brutalist and decorative) is the Markdown decorations. If you’re reading this post on the web, you’ll see a couple of things in the formatting of the body text are a bit unusual. The text of links is surrounded by square brackets and link destination is shown in parentheses, bold and italic text is wrapped in double asterisks and underlines respectively, headings have pound signs in front of them, etc. The goal of these styles is to replicate (to a certain extent) what the original, Markdown-formatted source of the blog posts looks like. With the exception of the heading pound signs, they’re entirely implemented in CSS using pseudo-elements, which has the added benefit that they’re not included in text highlighted/copied on the webpage. The pound signs next to subheadings in posts are the one exception to this and are actual tags because they serve a functional purpose: they provide jump links to the different subsections of posts. These are decorations and serve no functional purpose, but I still believe they’re brutalist because they reflect the raw content of the Markdown before it’s been processed. I think the decorations look nice and add some visual distinction to the design without being ostentatious or detracting from the experience of reading posts.</p>
<p>As for reading posts, I spent probably more time than I should have reading <a href="https://practicaltypography.com/" data-link="practicaltypography.com"><em>Practical Typography</em></a> and fiddling with the fonts, trying to make the typography as pleasant as possible for reading articles. While I appreciate the author’s point about not using <a href="https://practicaltypography.com/system-fonts.html" data-link="practicaltypography.com/system-fonts.htm…">system fonts</a>, for my own personal blog, I care more about speed and page weight than how unique the typography is. After much fiddling, I settled on Charter for the main body text and Avenir for the headings as well as the text in the header, footer, and comments sections. I chose them because they had the highest grade of any of the system fonts, and I think they look pretty good (both separately and together). Using a serif font for the body text, I find to be a much easier on the eyes compared to the sans serif font used by the previous design of this blog. A sans serif font for what I call the “UI” elements of the page provides a nice contrast with the body text. Code (both blocks and inline) uses Apple’s <a href="https://developer.apple.com/fonts/" data-link="developer.apple.com/fonts/">SF Mono</a> if it’s installed (because it’s my personal favorite) and falls back on the browser’s default monospace font, because I find most system monospace fonts either ugly or too light weight.</p>
<p>Regarding color schemes, there are still light and dark variations. But, as you’ll notice if you look at the theme selector at the bottom of the page, there’s now a new automatic theme (which is also the default). It uses the <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme" data-link="developer.mozilla.org/en-US/docs/Web/CSS…"><code>prefers-color-scheme</code></a> media query to support dynamically adapting to the user’s preferred color scheme across their operating system. For users on operating systems or browsers that don’t support the media query, light mode is used by default as before. Also, the new color scheme used for code blocks is the Atom One color scheme for <a href="https://highlightjs.org/" data-link="highlightjs.org">Highlight.js</a>.</p>
<h2 id="new-features"><a href="#new-features" class="header-anchor" aria-hidden="true" role="presentation">##</a> New Features</h2>
<p>There are a couple new features, in addition to the new automatic theme-switching. There are comments (discussed more <a href="#activity-pub" data-link="#activity-pub">below</a>). In addition to the <a href="/feed.xml" data-link="/feed.xml">main RSS feed</a>, there are now feeds specifically for the individual categories (e.g., the <a href="/meta/feed.xml" data-link="/meta/feed.xml">feed</a> for the meta category). There’s also an estimated reading time counter shown in the post metadata (it’s quite simple, it calculates the time by taking the word count divided by an average reading speed of 225 WPM<sup class="footnote-reference" id="fnref2"><a href="#2">[2]</a></sup>).</p>
<p>The site remains almost entirely JavaScript free. There are two places where client-side JS is used: the theme switcher and the comments section. There are ways to implement the theme switcher without client-side scripts, but I don’t believe it’s worth the trade off. The automatic theme is the default, and it’s what those with JS entirely disabled will get.</p>
<p>(There are also footnotes, which, so far at least, I’m getting a lot of mileage out of.)</p>
<h2 id="the-backend"><a href="#the-backend" class="header-anchor" aria-hidden="true" role="presentation">##</a> The Backend</h2>
<p>The previous version of my website used <a href="https://jekyllrb.com/" data-link="jekyllrb.com">Jekyll</a> (and WordPress before that, and Jekyll again before that). In what may become a pattern, I’ve once more switched away from Jekyll. Version Five uses something completely custom. It has been a work-in-progress in one form or another for about a year now. It started out as a Node.js project that was going to be a general-purpose static site generator. Then, around the time I was learning Elixir (which I love, and will be the subject of another blog post), I attempted to rewrite it in that<sup class="footnote-reference" id="fnref3"><a href="#3">[3]</a></sup>. Then we finally arrive at the current iteration of the current iteration of my website. In spite of my distaste for the ecosystem<sup class="footnote-reference" id="fnref4"><a href="#4">[4]</a></sup>, I returned to Node.js. This time, however, the project took a bit of a different direction than the previous two attempts at a rewrite. It has two main parts: the static site generator and the ActivityPub integration.</p>
<h3 id="static-site-generator"><a href="#static-site-generator" class="header-anchor" aria-hidden="true" role="presentation">###</a> Static Site Generator</h3>
<p>The static site generator is by far the most important piece. Without it, there would be no website. I once again went with an SSG for a couple reasons, starting and ending with performance. When it comes down to it, nothing is generated at request time. Everything exists as static files on disk that are generated when the service starts up. The basic architecture isn’t all that special: there are posts written in Markdown, gathered into various collections, rendered to HTML using various page layouts, and then gathered together in various indexes (the main index, category-specific ones, and RSS feeds).</p>
<p>The current iteration, however, was a bit different in its conception. Instead of attempting to be a general purpose static site generator that anyone could pick up and use, it is designed to be completely specific to my needs and the requirements for this blog. The different collections of posts (blog posts, the different tutorial series) are hardcoded in. The Markdown pipeline used to render posts to HTML is hardcoded, and contains some custom extensions for the Markdown decorations used. The list of which files are copied verbatim is hardcoded. You get the idea.</p>
<p>I’ve toyed with the idea of refactoring out all of the custom code I’m using to generate the blog (as well as the ActvityPub integration) into a separate library which other people could use for their own blogs. It’s not entirely off the table, but if it does happen, it won’t be soon. For now, I’m perfectly content with the functionality my pile of hardcoded, janky, custom code provides me. Besides, this project has been ongoing for more than a year, and I don’t need it to drag on any further.</p>
<h3 id="activity-pub"><a href="#activity-pub" class="header-anchor" aria-hidden="true" role="presentation">###</a> ActivityPub</h3>
<p>It may be mostly static (and could be used entirely statically), but there’s one big difference: it runs a web server that’s responsible for serving the static files and for handling the bit that actually needs to be dynamic: the ActivityPub integration. (There’s another blog post coming at some point about resources I found helpful in getting AP actually working with other platforms.)</p>
<p>If you haven’t heard of it before, <a href="https://activitypub.rocks" data-link="activitypub.rocks">ActivityPub</a> is the protocol that underpins the ‘fediverse,’ a network of federated software projects. Most of the current projects are aimed at being replacements for traditional social networks: <a href="https://joinmastodon.org" data-link="joinmastodon.org">Mastodon</a> and <a href="https://pleroma.social" data-link="pleroma.social">Pleroma</a> are microblogging implementations similar to Twitter, <a href="https://pixelfed.org" data-link="pixelfed.org">Pixelfed</a> is a photo sharing platform similar to Instagram, and <a href="https://joinpeertube.org/" data-link="joinpeertube.org">PeerTube</a> is a video hosting service like YouTube. There are also blog engines that federate using ActivityPub, including <a href="https://joinplu.me" data-link="joinplu.me">Plume</a> and <a href="https://writefreely.org" data-link="writefreely.org">WriteFreely</a>, which were the inspiration for this project.</p>
<p>Could I have used Plume or WriteFreely instead of rolling my own custom solution? Yeah, probably. But I have no doubt I would have run into substantial problems along the way. (Off the top of my head, probably ones related to having separate collections for tutorial series as well as the Markdown decorations.)</p>
<p>The current ActivityPub is pretty bare bones. It implements the minimum necessary to allow: 1) looking up posts, 2) following the blog from other AP-implementing services, and 3) commenting on posts. You may be able to do other things with my posts from other AP services (e.g., favoriting and reblogging), but the code on my end doesn’t know or care. In fact, only handles a whopping four types of incoming activities: Follow, Create (only for Note objects), Delete (also only for Notes), and Undo (only for Follows). This may be expanded in the future, but for now I’m content with the functionality it provides.</p>
<p>The way I’ve set up comments to work here is pretty simple. The underlying ActivityPub objects are stored in the database, and whenever someone loads the page and expands the comment section, some client-side JavaScript is triggered. It sends a request to the back end to retrieve all the comments for the given post from the database, then turns them into a tree, renders them to HTML, and injects them into the page. This does mean that viewing comments requires JavaScript, but the other option would have been to have request pages be generated upon each request, making them no longer static pages. Given the trade offs, I opted to keep the article pages static and retain the performance benefits that brings. I think the compromise is worth it; most people have JavaScript enabled and those who don’t probably aren’t looking at the comments.</p>
<p>If you want to comment on any of my blog posts, just copy and paste the URL into the search field of your client and hit the reply button!</p>
<h3 id="technical-details"><a href="#technical-details" class="header-anchor" aria-hidden="true" role="presentation">###</a> Technical Details</h3>
<p>If you’re interested, here are some of the technical details about how the back end is implemented.</p>
<p>All of the backend stuff is written in Node.js and TypeScript. The SSG piece uses Markdown (with some extensions) for rendering posts, Sass (specifically SCSS) for the styles, and EJS for the templates. The ActivityPub integration uses Postgres with TypeORM to store remote actors and comments. And the web server itself is Express.js.</p>
<p>When the program starts, the static site generation is performed before the web server is started to ensure that broken or outdated files aren’t served. First, some files are copied over verbatim (such as favicons and the client-side JS for loading comments), the CSS files for the individual themes are compiled from the Sass files, and the error pages are generated. Then, tutorials and blog posts are generated by scanning through the respective directories and rendering any Markdown files. Finally, the home page, category index pages, and RSS feeds are generated. </p>
<p>That’s it for the generation, but before the web server starts, the ActivityPub module takes all of the posts, checks if there are any new ones, and, if so, adds them to the database and federates them out to all the remote actors following the blog. Then, all the web server routes get set up and the server finally starts.</p>
<p>By the way, the source code for the generator, ActivityPub integration, and content is self is all visible <a href="https://git.shadowfacts.net/shadowfacts/shadowfacts.net" data-link="git.shadowfacts.net/shadowfacts/shadowfa…">on my Gitea</a>. (Note: the source for the generator and AP integration (the <code>lib</code> directory) is open source whereas the contents of my website (the <code>site</code> directory) is only <em>visible</em> source.)</p>
<h2 id="conclusion"><a href="#conclusion" class="header-anchor" aria-hidden="true" role="presentation">##</a> Conclusion</h2>
<p>A lot of stuff has technical, under the hood stuff changes, and I’d like to think that I haven’t wasted my time and that I’ll actually use this new version of my blog to publish posts. But, I don’t know what will happen and I can’t make any promises. I have some drafts of posts that I’d like to finish and finally publish, so stay tuned (you can subscribe on <a href="/feed.xml" data-link="/feed.xml">RSS</a> or by following <code>@blog@shadowfacts.net</code> on your favorite ActivityPub platform). As for the Minecraft modding tutorial series, those have been discontinued. They remain available for posterity, but they haven’t been updated (merely transplanted into the new blog), and I don’t currently have any plans to write new ones.</p>
<hr class="footnotes-sep"><section class="footnotes"><div id="1" class="footnote-item"><span class="footnote-marker">1.</span>
<p>Also excellent is the <a href="http://bettermotherfuckingwebsite.com/" data-link="bettermotherfuckingwebsite.com">Better Motherfucking Website</a>. <a href="#fnref1" class="footnote-backref">↩</a></p>
</div><div id="2" class="footnote-item"><span class="footnote-marker">2.</span>
<p>My sources for that number are pretty bad. It’s based on the reading time things from various other blogging engines. If you have better sources, let me know. <a href="#fnref2" class="footnote-backref">↩</a></p>
</div><div id="3" class="footnote-item"><span class="footnote-marker">3.</span>
<p>Unfortunately, this attempt ran into some issues fairly quickly. Elixir itself is wonderful, but the package ecosystem for web-related things such as Sass, Markdown rendering, and syntax highlighting, is lackluster. <a href="#fnref3" class="footnote-backref">↩</a></p>
</div><div id="4" class="footnote-item"><span class="footnote-marker">4.</span>
<p>The <code>package.json</code> for the project explicitly lists 30  dependencies, 13 of which are TypeScript type definitions. There are 311 packages in my <code>node_modules</code> folder. Enough said. <a href="#fnref4" class="footnote-backref">↩</a></p>
</div></section>]]></content:encoded>
    </item>
    <item>
      <title>Comments Powered by GitHub</title>
      <link>https://shadowfacts.net/2017/comments-powered-by-git-hub/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2017/comments-powered-by-git-hub/</guid>
      <pubDate>Sun, 23 Apr 2017 13:05:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><strong>NOTE:</strong> This article has been superseded by the <a href="/2019/reincarnation/#activity-pub" data-link="/2019/reincarnation/#activity-pub">ActivityPub comments system</a>.</p>
<p>After seeing <a href="http://donw.io/post/github-comments/" data-link="donw.io/post/github-comments/">this article</a> the other morning about replacing the Disqus comments on a blog powered by a static site generator (like this one) with comments backed by a GitHub issue and some front-end JavaScript to load and display them, I thought it would be fun to implement something similar. First I only built the code for displaying comments, similar to the aforementioned article, but I decided to take it one step further by allowing users to submit comments directly from my site.</p>
<!-- excerpt-end -->
<p>You might be wondering, <em>Why even use weird, front-end comments like this when you could just use a database on the backend and generate the page dynamically?</em> Well, there are a couple reasons:</p>
<p>Firstly, it’s a lot simpler to code (I don’t have to handle any of the backend stuff like storage/moderation tools/etc.)</p>
<p>Secondly, and more important, my site can remain entirely static. This second reason was the key factor for me. Being static allows my site to be hosted for free on <a href="https://pages.github.com/" data-link="pages.github.com">GitHub Pages</a> so I don’t have to handle any of the server-side stuff myself. It also makes the site ridiculously fast. A draft of this post has all the content and styles loaded within ~150ms and has the comments loaded after ~300ms.</p>
<p>So, how did I implement it? Well the first part is fairly simple and based on the <a href="http://donw.io/post/github-comments/" data-link="donw.io/post/github-comments/">original article</a>. It simply sends a request to the GitHub API <a href="https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue" data-link="developer.github.com/v3/issues/comments/">endpoint</a>, parses the resulting JSON, generates some HTML, and injects it back into the page.</p>
<p>The second part is a bit more complicated, as it handles authentication with the GitHub API and posting comments directly from my site. Since this is a fair bit more complicated with several possible paths to the desired behavior, I’ll go through this (twice, the reasoning for which will become clear soon) as would actually happen:</p>
<ol>
<li>The user  enters some comment into the textarea and clicks the submit button. At this point, since the user’s never submitted a comment via the website before, we need to authorize with GitHub before we can submit.</li>
<li>When the user clicks the submit button, the forms submits to a separate backend helper application that handles the OAuth authorization flow.</li>
<li>The server app then temporarily stores the submitted comment with a random ID and redirects the user to the GitHub authorization page where the user grants access to their account.</li>
<li>From there, GitHub redirects back to the helper app with the same random ID and an OAuth code.</li>
<li>The helper app then sends a request to GitHub with the OAuth code, client ID, and client secret (this is why the helper is necessary, to keep the secret secret) getting an authorization token in response.</li>
<li>The helper app uses the random ID to retrieve the comment being submitted and the URL being submitted from, redirecting the user back to the original URL with the comment and auth token in the URL hash.</li>
<li>The client loads the comment and auth token from the hash and the clears the hash.</li>
<li>The auth token is stored in a cookie for future use.</li>
<li>Finally, the client then sends a POST request to the GitHub API <a href="https://developer.github.com/v3/issues/comments/#create-a-comment" data-link="developer.github.com/v3/issues/comments/">endpoint</a> with the comment, issue ID, and the token to submit the comment.</li>
</ol>
<p>This is the flow for when the client’s never submitted a comment before, but as stated in step 8, the auth token is cached on the client, making things simpler the next time someone wants to submit a comment. When the comment submit button is pressed and there is an auth token cached, we simply cancel the form submission and send the POST request to GitHub, submitting the comment.</p>
<p>All of the code for this is open source. The front-end JS is available <a href="https://github.com/shadowfacts/shadowfacts.github.io/blob/master/js/comments.js" data-link="github.com/shadowfacts/shadowfacts.githu…">here</a> and the backend GitHub API helper is <a href="https://github.com/shadowfacts/gh-comment-poster" data-link="github.com/shadowfacts/gh-comment-poster">here</a>.</p>
<p>And that’s it! So, do you like this system? Hate it? Have suggestions for how it could be improved? Well now you can leave a comment.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Pretty Good Minor Update</title>
      <link>https://shadowfacts.net/2017/the-pretty-good-minor-update/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2017/the-pretty-good-minor-update/</guid>
      <pubDate>Fri, 17 Feb 2017 18:30:42 +0000</pubDate>
      <content:encoded><![CDATA[<p>It’s been about six months since the last time I redesigned the site, and while I didn’t want to redesign it yet again, I felt it could use a little update to make sure everything’s still good.</p>
<!-- excerpt-end -->
<p>After reading this <a href="http://jacquesmattheij.com/the-fastest-blog-in-the-world" data-link="jacquesmattheij.com/the-fastest-blog-in-…">blog post</a> about optimizing sites (specifically using a static site generator, like this one does) that was posted on HN, I got to thinking about optimizing my site. I tested my site on Google’s <a href="https://developers.google.com/speed/pagespeed/insights" data-link="developers.google.com/speed/pagespeed/in…">PageSpeed Insights</a> and got a mediocre score (I don’t recall the exact number, but it was in the 70s or 80s).  I haven’t gone anywhere near as all-out with the optimization as the blog post described, but I’ll still go over the couple things I did do:</p>
<ul>
<li>Removing custom fonts. The only custom font I previously used was <a href="https://github.com/chrissimpkins/Hack" data-link="github.com/chrissimpkins/Hack">Hack</a> for code blocks, so removing that shaved off several extra requests and quite a bit of time without changing much.</li>
<li>Replace <a href="https://github.com/js-cookie/js-cookie" data-link="github.com/js-cookie/js-cookie">js-cookie</a> with a <a href="https://github.com/shadowfacts/shadowfacts.github.io/blob/master/_includes/head.html#L17-L34" data-link="github.com/shadowfacts/shadowfacts.githu…">couple functions</a> included in the inline script, saving ~2 kilobytes and an additional HTTP request.</li>
<li><a href="https://www.cloudflare.com" data-link="cloudflare.com">CloudFlare</a> provides a number of optimizations (caching and HTML/CSS minification).</li>
</ul>
<p>Additionally, there’s now a fallback <code>&lt;noscript&gt;</code> tag so that if the viewer has JavaScript disabled, the site will still look normal (JavaScript being disabled does mean the theme can’t be changed, so the user will always see the light theme). And lastly, there’s now a custom 404 page so if you end up at the wrong URL, you’ll see something nicer than the default 404 page for GitHub Pages.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Type: A FOSS clone of typing.io</title>
      <link>https://shadowfacts.net/2016/type/</link>
      <category>misc</category>
      <guid>https://shadowfacts.net/2016/type/</guid>
      <pubDate>Sat, 08 Oct 2016 21:29:42 +0000</pubDate>
      <content:encoded><![CDATA[<p><strong>TL;DR</strong>: I made an awesome FOSS clone of <a href="https://typing.io" data-link="typing.io">typing.io</a> that you can check out at <a href="https://type.shadowfacts.net" data-link="type.shadowfacts.net">type.shadowfacts.net</a> and the source of which you can see <a href="https://github.com/shadowfacts/type" data-link="github.com/shadowfacts/type">here</a>.</p>
<p>I’ve used <a href="https://typing.io" data-link="typing.io">typing.io</a> on and off for almost a year now, usually when I’m bored and have nothing else to do. Unfortunately, I recently completed the Java file, the C++ file, and the JavaScript file (that last one took too much time, jQuery has weird coding formatting standards, IMO) meaning I’ve completed pretty much everything that interests me. Now if you want to upload your own code to type, you have to pay $9.99 <em>a month</em>, which, frankly, is ridiculous. $10 a month to be able to upload code to a website only to have more than the 17 default files (one for each langauge) when I could build my own clone.</p>
<!-- excerpt-end -->
<p>This is my fourth attempt at building a clone of typing.io, and the first one that’s actually been successful. (The first, second, and third all failed because I was trying to make a syntax highlighting engine work with too much custom code.)</p>
<p>Type uses <a href="https://codemirror.net/" data-link="codemirror.net">CodeMirror</a>, a fantastic (and very well documented) code editor which handles <a href="#syntax-highlighting" data-link="#syntax-highlighting">syntax highlighting</a>, <a href="#themes" data-link="#themes">themes</a>, <a href="#cursor-handling" data-link="#cursor-handling">cursor handling</a>, and <a href="#input" data-link="#input">input</a>.</p>
<h2 id="input"><a href="#input" class="header-anchor" aria-hidden="true" role="presentation">##</a> Input</h2>
<p>Input was one of the first things I worked on. (I wanted to get the very basics working before I got cought up in minor details.) CodeMirorr’s normal input method doesn’t work for me, because in Type, all the text is in the editor beforehand and the user doesn’t actually type it out. The CodeMirror instance is set to <code>readOnly</code> mode, making entering or removing text impossible. This is all well and good, but how can you practice typing if you can’t type? Well, you don’t actually type. The DOM <code>keypress</code> and <code>keydown</code> events are used to handle character input and handle special key input (return, backspace, tab, and escape) respectively. </p>
<p>The <code>keypress</code> event handler simply moves the cursor one character and marks the typed character as completed. If the character the user typed isn’t the character that’s in the document they are typing, a CodeMirror <a href="http://codemirror.net/doc/manual.html#markText" data-link="codemirror.net/doc/manual.html">TextMarker</a> with the <code>invalid</code> class will be used to display a red error-highlight to the user. These marks are then stored in a 2-dimensional array which is used to check if the user has actully completed the file.</p>
<p>The <code>keydown</code> event is used for handling special key pressed namely, return, backspace, delete, and escape. </p>
<p>When handling a return press, the code first checks if the user has completed the current line (This is a little bit more complicated than checking if the cursor position is at the end of the line, because Type allows you to skip typing whitespace at the beggining and end of lines because every IDE/editor under the sun handles that for you). Then, the editor moves the cursor to the beggining of the next line (see the previous parenthetical).</p>
<p>Backspace handling works much the same way, checking if the user is at the begging of the line, and if so, moving to the end of the previous line, or otherwise moving back 1 character. Delete also has a bit of extra functionality specific to Type. Whenever you press delete and the previous character was marked as invalid, the invalid marks needs to A) be cleared from the CodeMirror document and B) removed from the 2D array of invalid marks that’s used for completion checking.</p>
<p>The tab key requires special handling because it’s not entered as a normal character and therefore special checking has to be done to see if the next character is a tab character. Type doesn’t handling using the tab key with space-indentation like most editors/IDEs because most places where you’d encounter significant amounts of whitespace in the middle of a line, it’s a tab character used to line up text across multiple lines.</p>
<p>Escape is handled fairly simply. When escape is pressed and the editor is focused, a global <code>focus</code> variable is toggled, causing all other input-handling to be disabled, a <strong><code>Paused</code></strong> label is added/removed in the top right of the top bar, and lastly the <code>paused</code> class is toggled on the page, which, when active, gives the editor 50% opacity, giving it a nice effect that clearly indicates the paused state.</p>
<h2 id="cursor-handling"><a href="#cursor-handling" class="header-anchor" aria-hidden="true" role="presentation">##</a> Cursor Handling</h2>
<p>Preventing the cursor movement (something you obviously don’t want for a typing practice thing) that’s still possible, even in CodeMirror’s read only mode, is accomplished simply by adding an event listener on the CodeMirror <code>mousedown</code> event  and calling <code>preventDefault</code> on the event to prevent the editor’s default behavior from taking place and calls the <code>focus</code> method on the editor instance, focusing it and un-pauses it if it was previously paused. </p>
<h2 id="syntax-highlighting"><a href="#syntax-highlighting" class="header-anchor" aria-hidden="true" role="presentation">##</a> Syntax Highlighting</h2>
<p>Syntax highlighting is handled completely using CodeMirror’s <a href="http://codemirror.net/mode/index.html" data-link="codemirror.net/mode/index.html">modes</a>, so Type supports* everything that CodeMirror does. By default, Type will try to automatically detect a language mode to use based on the file’s extension, falling back to plain-text if a mode can’t be found. This is accomplished by searching the (ridiculously large and manually written) <a href="https://github.com/shadowfacts/type/blob/master/js/languages.js" data-link="github.com/shadowfacts/type/blob/master/…">langauges map</a> that stores A) the JS CodeMirror mode file to load, B) the MIME type to pass to CodeMirror, and C) the extensions for that file-type (based on GitHub <a href="https://github.com/github/linguist" data-link="github.com/github/linguist">linguis</a> data). Yes, I spent far too long manually writing that file when I probably could have <a href="https://xkcd.com/1319/" data-link="xkcd.com/1319/">automated</a> it. The script for the mode is then loaded using <code>jQuery.getScript</code>and the code, along with the MIME type, and a couple other things, are passed into <code>CodeMirror.fromTextArea</code>.</p>
<p>* Technically it does, however only a subset of those languages can actually be used because they seem common enough** to warrant being manually added to the languages map.</p>
<p>** I say “common” but <a href="https://github.com/shadowfacts/type/blob/master/js/languages.js#L2" data-link="github.com/shadowfacts/type/blob/master/…">Brainfuck</a> and <a href="https://github.com/shadowfacts/type/blob/master/js/languages.js#L142" data-link="github.com/shadowfacts/type/blob/master/…">FORTRAN</a> aren’t really common, I just added them for shits and giggles.</p>
<h2 id="themes"><a href="#themes" class="header-anchor" aria-hidden="true" role="presentation">##</a> Themes</h2>
<p>Themes are handled fairly similarly to syntax highlighting. There’s a massive <code>&lt;select&gt;</code> dropdown which contains all the options for the <a href="https://github.com/codemirror/CodeMirror/tree/master/theme" data-link="github.com/codemirror/CodeMirror/tree/ma…">CodeMirror themes</a>. When the dropdown is changed, the stylesheet for the selected theme is loaded and the <code>setTheme</code> function is called on the editor.</p>
<h2 id="chunks"><a href="#chunks" class="header-anchor" aria-hidden="true" role="presentation">##</a> Chunks</h2>
<p>Chunks were the ultimate solution to a problem I ran into fairly early on when I was testing Type. Due to the way Type handles showing which parts of the file haven’t been completed (having a single <code>TextMarker</code> going from the cursor to the end of the file and updating it when the cursor moves), performance suffers a lot for large files because of the massive amount of DOM updates and re-renders when typing quickly. The solution I came up with was splitting each file up into more managable chunks (50 lines, at most) which can more quickly be re-rendered by the browser. Alas, this isn’t a perfect solution because CodeMirror’s lexer can sometimes break with chunks (see <a href="#fixing-syntax-highlighting" data-link="#fixing-syntax-highlighting">Fixing Syntax Highlighting</a>) , but it’s the best solution I’ve come up with so far.</p>
<h2 id="storage"><a href="#storage" class="header-anchor" aria-hidden="true" role="presentation">##</a> Storage</h2>
<p>One of the restrictions I imposed on myself for this project (mostly because I didn’t want to pay for a server) was that Type’s functionality had to be 100% client-side only. There are two primary things that result from this 1) Type is account-less and 2) therefore everything (progress, current files, theme, etc.) have to be stored client-side.</p>
<p>I decided to use Mozilla’s <a href="https://github.com/localForage/localForage" data-link="github.com/localForage/localForage">localForage</a> simply because I remembered it when I had to implement storage stuff. (If you don’t know, localForage is a JS wrapper around IndexedDB and/or WebSQL with a fallback to localStorage which makes client-side persistence much nicer.)</p>
<p>Basic overview of what Type stores:</p>
<pre>
root
|
+-->theme
|
+-->owner/repo/branch
	|
	+-->path/to/file
		|
		+-->chunk
		|
		+-->chunks array
			|
			+-->0
				|
				+-->cursor
				|   |
				|   +-->line
				|   |
				|   +-->ch
				|
				+-->elapsedTime
				|
				+-->invalids array
					|
					+-->invalids array
						|
						+-->0
							|
							+-->line
							|
							+-->ch
</pre>
<p>If you want to see actually what Type stores, feel free to take a look in the Indexed DB section of the Application tab of the Chrome web inspector (or the appropriate section of your favorite browser).</p>
<h2 id="wpm-tracking"><a href="#wpm-tracking" class="header-anchor" aria-hidden="true" role="presentation">##</a> WPM Tracking</h2>
<p>WPM tracking takes place primarily in the <a href="https://github.com/shadowfacts/type/blob/master/js/type.js#L561" data-link="github.com/shadowfacts/type/blob/master/…"><code>updateWPM</code></a> function which is called every time the user presses return to move to the next line. <code>updateWPM</code> does a number of things.</p>
<ol>
<li>If the editor is focused, it updates the elapsed time.</li>
<li>It gets the total number of words in the chunk. This is done by splitting the document text with a regex that matches A) any whitespace character B) a comma C) a period and getting the length of the resulting array.</li>
<li>Getting the total number of minutes from the elapsed time (which is stored in miliseconds).</li>
<li>The WPM indicator is updated (# of words / # of minutes).</li>
</ol>
<h1 id="what-s-next"><a href="#what-s-next" class="header-anchor" aria-hidden="true" role="presentation">#</a> What’s Next</h1>
<p>Right now, Type is reasonably complete. It’s in a perfectly useable state, but there are still more things I want to do.</p>
<h2 id="fixing-syntax-highlighting"><a href="#fixing-syntax-highlighting" class="header-anchor" aria-hidden="true" role="presentation">##</a> Fixing Syntax Highlighting</h2>
<p>Because of the chunking system, in some cases syntax highlighting is utterly broken because a key thing that the lexer needs to understand what the code is isn’t present because it’s in the previous chunk. One relatively common example of this is block-comments. If a block-comment begins in one chunk but terminates in a later chunk, the text that’s inside the comment but in a different chunk than the starting mark has completely invalid highlighting because the lexer has no idea it’s a comment.</p>
<h2 id="skipping-comments"><a href="#skipping-comments" class="header-anchor" aria-hidden="true" role="presentation">##</a> Skipping Comments</h2>
<p>This is a really, really nice feature that typing.io has which is that as you’re typing, the cursor will completely skip over comments, both on their own lines and on the ends of other lines. This should be possible, I just need to hook into CodeMirror’s syntax highlighting code and find out if the next thing that should be typed is marked as a comment and if so, skip it.</p>
<h2 id="polishing"><a href="#polishing" class="header-anchor" aria-hidden="true" role="presentation">##</a> Polishing</h2>
<p>If you’ve looked at the site, you can tell it’s fairly unpolished. It’s got almost no styling and is fairly unintuitive. It’ll probably remain minimalistic, but it’d be nice to have unified design/theme across the entire site.</p>
<h2 id="typo-heatmap"><a href="#typo-heatmap" class="header-anchor" aria-hidden="true" role="presentation">##</a> Typo Heatmap</h2>
<p>This is a feature in the premium version of typing.io that I’d like to add to Type. It shows a heat map of all the keys you make errors on. The only thing that’s preventing me from working on this currently is it would require manually writing a massive data file containing all the locations of all the keys and which characters correspond to which keys, something I don’t want to do after spending too much time manually writing the <a href="https://github.com/shadowfacts/type/blob/master/js/languages.js" data-link="github.com/shadowfacts/type/blob/master/…">language map</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>The Great Redesign</title>
      <link>https://shadowfacts.net/2016/the-great-redesign/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2016/the-great-redesign/</guid>
      <pubDate>Sun, 07 Aug 2016 19:39:48 +0000</pubDate>
      <content:encoded><![CDATA[<p>Welcome to the fourth iteration of my website. I’m still using Jekyll, however I’ve rewritten most of the styles from scratch. This theme is based on the <a href="https://github.com/CodeDaraW/Hacker" data-link="github.com/CodeDaraW/Hacker">Hacker theme</a> for <a href="https://hexo.io/" data-link="hexo.io">Hexo</a> which is turn based on the <a href="https://wordpress.org/themes/hacker/" data-link="wordpress.org/themes/hacker/">Hacker WordPress theme</a> but it has some notable differences.</p>
<!-- excerpt-end -->
<h3 id="1-it-s-built-for-jekyll-"><a href="#1-it-s-built-for-jekyll-" class="header-anchor" aria-hidden="true" role="presentation">###</a> 1. It’s built for Jekyll.</h3>
<p>Because Jekyll (and more specifically, GitHub Pages) uses Sass instead of <a href="https://github.com/tj/styl" data-link="github.com/tj/styl">Styl</a> like Hacker, all of the styles had to be rewritten from scratch in SCSS. Most of the original <a href="https://github.com/jekyll/minima" data-link="github.com/jekyll/minima">Minima</a> styles were scrapped, except for a couple of code styling details and the footer design.</p>
<h3 id="2-it-has-a-dark-theme"><a href="#2-it-has-a-dark-theme" class="header-anchor" aria-hidden="true" role="presentation">###</a> 2. It has a dark theme</h3>
<p>This is accomplished storing the current them (<code>dark</code> or <code>light</code>) in a cookie, reading it in the head, and writing a <code>&lt;link&gt;</code> element based on the the value of the theme. All the styles are stored in <code>_sass/theme.scss</code> and the <code>css/light.scss</code> and <code>css/dark.scss</code> files store the variable definitions for all the colors used in the theme. Jekyll then compiles the two main SCSS files into two CSS files that each contain <a href="https://necolas.github.io/normalize.css/" data-link="necolas.github.io/normalize.css/">Normalize.css</a>, the theme (compiled from the variable definitions), and the <a href="https://github.com/shadowfacts/RougeDarcula" data-link="github.com/shadowfacts/RougeDarcula">Darcula</a> syntax highlighting theme.</p>
<p>While this does increase the load time and isn’t best practice, I think providing the option of a dark theme (especially when the deafult theme is incredibly light (the majority of the page is pure white (ooh, tripple nested parentheses))) outweights the cost. Besides, when testing locally the entire script loading and executiononly cost 5 miliseconds, completely unnoticable.</p>
<p>The selector in the third column of the footer simply updates the cookie value based on the checkbox status and reloads the page via <code>window.location.reload()</code> triggering the changed theme CSS to be loaded.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Kotlin and Minecraft Forge</title>
      <link>https://shadowfacts.net/2016/kotlin-and-minecraft-forge/</link>
      <category>minecraft</category>
      <guid>https://shadowfacts.net/2016/kotlin-and-minecraft-forge/</guid>
      <pubDate>Sat, 06 Aug 2016 20:45:30 +0000</pubDate>
      <content:encoded><![CDATA[<p>So, you wanna use <a href="https://kotlinlang.org/" data-link="kotlinlang.org">Kotlin</a> in your Forge mod? Well there’s good news, I’ve just released <a href="https://github.com/shadowfacts/Forgelin" data-link="github.com/shadowfacts/Forgelin">Forgelin</a>, a fork of <a href="https://github.com/Emberwalker/Forgelin" data-link="github.com/Emberwalker/Forgelin">Emberwalker’s Forgelin</a>, a library that provides utilities for using Kotlin with Minecraft/Forge. </p>
<p>Forgelin provides a Kotlin langauge adapter that allows your main-mod class to be a <a href="https://kotlinlang.org/docs/reference/object-declarations.html" data-link="kotlinlang.org/docs/reference/object-dec…"><code>object</code></a>. In order to use the language adapter, you must specify the <code>modLanguageAdapter</code> property in your <code>@Mod</code> annotation to be <code>net.shadowfacts.forgelin.KotlinAdapter</code>.</p>
<!-- excerpt-end -->
<p>Additionally, Forgelin repackages the Kotlin standard library, reflect library, and runtime so that you don’t have to, and so that end users don’t have to download the 3 megabytes of Kotlin libraries multiple times.</p>
<p><del>Additionally, Forgelin provides a number of <a href="https://kotlinlang.org/docs/reference/extensions.html" data-link="kotlinlang.org/docs/reference/extensions…">extensions</a> (which are viewable <a href="https://github.com/shadowfacts/Forgelin/tree/master/src/main/kotlin/net/shadowfacts/forgelin/extensions" data-link="github.com/shadowfacts/Forgelin/tree/mas…">here</a>) for working with Minecraft/Forge.</del></p>
<p><del>While you can shade Forgelin, it is not recommended to do so. It will increase your jar size by approximately 3 megabytes (as Forgelin itself includes the entire Kotlin, standard lib, reflect lib, and runtime) and may cause issues with other mods that shade Kotlin or Forgelin. It is recommended that you have your users download Forgelin from <a href="https://minecraft.curseforge.com/projects/shadowfacts-forgelin" data-link="minecraft.curseforge.com/projects/shadow…">CurseForge</a>.</del></p>
<p><strong>Update Feb 17, 2017:</strong> </p>
<ol>
<li>As of Forgelin 1.1.0, the extensions have been moved from Forgelin to <a href="https://github.com/shadowfacts/ShadowMC/tree/1.11.2/src/main/kotlin/net/shadowfacts/forgelin/extensions" data-link="github.com/shadowfacts/ShadowMC/tree/1.1…">ShadowMC</a>.</li>
<li>As of Forgelin 1.3.0, Forgelin includes an <code>@Mod</code> annotated object. This means:
<ol>
<li><strong>Forgelin can no longer be shaded.</strong></li>
<li><code>required-after:forgelin;</code> can now be used in the <code>dependencies</code> field of your <code>@Mod</code> annotation for a nicer error message when Forgelin isn’t installed.</li>
</ol>
</li>
</ol>
<p>A bare-bones example mod using Forgelin is available <a href="https://github.com/shadowfacts/ForgelinExample" data-link="github.com/shadowfacts/ForgelinExample">here</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Introducing Mirror</title>
      <link>https://shadowfacts.net/2016/introducing-mirror/</link>
      <category>java</category>
      <guid>https://shadowfacts.net/2016/introducing-mirror/</guid>
      <pubDate>Thu, 28 Jul 2016 20:45:00 +0000</pubDate>
      <content:encoded><![CDATA[<p>Allow me to introduce my latest project, Mirror. Mirror is a <a href="https://en.wikipedia.org/wiki/Reflection_(computer_programming)" data-link="en.wikipedia.org/wiki/Reflection_(comput…">reflection</a> library for Java designed to take advantage of the streams, lambdas, and optionals introduced in Java 8.</p>
<!-- excerpt-end -->
<p>The source code is publicly available on <a href="https://github.com/shadowfacts/Mirror/" data-link="github.com/shadowfacts/Mirror/">GitHub</a> under the MIT license and the JavaDocs are viewable <a href="https://shadowfacts.net/Mirror/" data-link="shadowfacts.net/Mirror/">here</a>.</p>
<h2 id="installation"><a href="#installation" class="header-anchor" aria-hidden="true" role="presentation">##</a> Installation</h2>
<p>All version of Mirror are <a href="http://mvn.rx14.co.uk/shadowfacts/net/shadowfacts/Mirror" data-link="mvn.rx14.co.uk/shadowfacts/net/shadowfac…">available on my Maven</a>.</p>
<h3 id="maven"><a href="#maven" class="header-anchor" aria-hidden="true" role="presentation">###</a> Maven</h3><pre class="highlight" data-lang="plaintext"><code>&lt;repositories&gt;
	&lt;repository&gt;
		&lt;id&gt;shadowfacts&lt;/id&gt;
		&lt;url&gt;http://mvn.rx14.co.uk/shadowfacts/&lt;/url&gt;
	&lt;/repository&gt;
&lt;/repositories&gt;

&lt;dependency&gt;
	&lt;groupId&gt;net.shadowfacts&lt;/groupId&gt;
	&lt;artifactId&gt;Mirror&lt;/artifactId&gt;
	&lt;version&gt;1.0.0&lt;/version&gt;
&lt;/dependency&gt;
</code></pre><h3 id="gradle"><a href="#gradle" class="header-anchor" aria-hidden="true" role="presentation">###</a> Gradle</h3><pre class="highlight" data-lang="plaintext"><code>repositories {
	maven {
		name &quot;shadowfacts&quot;
		url &quot;http://mvn.rx14.co.uk/shadowfacts/&quot;
	}
}

dependencies {
	compile group: &quot;net.shadowfacts&quot;, name: &quot;Mirror&quot;, version: &quot;1.0.0&quot;
}
</code></pre><h2 id="usage"><a href="#usage" class="header-anchor" aria-hidden="true" role="presentation">##</a> Usage</h2>
<p>A couple of simple examples for getting started with Mirror.</p>
<p>For more complex examples of everything possible with Mirror, you can look at the <a href="https://github.com/shadowfacts/Mirror/tree/master/src/test/java/net/shadowfacts/mirror" data-link="github.com/shadowfacts/Mirror/tree/maste…">unit tests</a>.</p>
<h3 id="general-overview"><a href="#general-overview" class="header-anchor" aria-hidden="true" role="presentation">###</a> General Overview</h3>
<p>The <code>Mirror.of</code> methods are used to retrieve mirrors on which operations can be performed. The types of mirrors are:</p>
<ul>
<li><a href="https://shadowfacts.net/Mirror/net/shadowfacts/mirror/MirrorClass.html" data-link="shadowfacts.net/Mirror/net/shadowfacts/m…"><code>MirrorClass</code></a></li>
<li><a href="https://shadowfacts.net/Mirror/net/shadowfacts/mirror/MirrorEnum.html" data-link="shadowfacts.net/Mirror/net/shadowfacts/m…"><code>MirrorEnum</code></a></li>
<li><a href="https://shadowfacts.net/Mirror/net/shadowfacts/mirror/MirrorConstructor.html" data-link="shadowfacts.net/Mirror/net/shadowfacts/m…"><code>MirrorConstructor</code></a></li>
<li><a href="https://shadowfacts.net/Mirror/net/shadowfacts/mirror/MirrorMethod.html" data-link="shadowfacts.net/Mirror/net/shadowfacts/m…"><code>MirrorMethod</code></a></li>
<li><a href="https://shadowfacts.net/Mirror/net/shadowfacts/mirror/MirrorField.html" data-link="shadowfacts.net/Mirror/net/shadowfacts/m…"><code>MirrorField</code></a></li>
</ul>
<p>The <code>Mirror.ofAll</code> methods are used to create mirror stream wrappers for a given stream/collection/array of reflection objects or mirrors.</p>
<p>These examples will use the following classes:</p>
<pre class="highlight" data-lang="java"><code><span class="hl-kw">public</span> <span class="hl-kw">class</span> <span class="hl-type">Test</span> {
	<span class="hl-kw">public</span> <span class="hl-kw">static</span> <span class="hl-type">String</span> <span class="hl-var">name</span> = <span class="hl-str">&quot;Mirror&quot;</span>;
	<span class="hl-kw">public</span> <span class="hl-kw">static</span> <span class="hl-type">String</span> <span class="hl-var">author</span>;

	<span class="hl-kw">public</span> <span class="hl-kw">static</span> <span class="hl-type">String</span> <span class="hl-fn">reverse</span>(<span class="hl-type">String</span> <span class="hl-var">str</span>) {
		<span class="hl-kw">return</span> <span class="hl-kw">new</span> <span class="hl-type">StringBuilder</span>(<span class="hl-var">str</span>).<span class="hl-fn">reverse</span>().<span class="hl-fn">toString</span>();
	}
}

<span class="hl-kw">public</span> <span class="hl-kw">class</span> <span class="hl-type">Test2</span> {
	<span class="hl-kw">public</span> <span class="hl-kw">static</span> <span class="hl-type">String</span> <span class="hl-var">name</span> = <span class="hl-str">&quot;Test 2&quot;</span>;

	<span class="hl-kw">public</span> <span class="hl-kw">static</span> <span class="hl-builtin">void</span> <span class="hl-fn">doSomething</span>() {
	}
}
</code></pre><h3 id="getting-fields"><a href="#getting-fields" class="header-anchor" aria-hidden="true" role="presentation">###</a> Getting Fields</h3><pre class="highlight" data-lang="java"><code><span class="hl-cmt">// get the field</span>
<span class="hl-type">Optional</span>&lt;<span class="hl-type">MirrorField</span>&gt; <span class="hl-var">optional</span> = <span class="hl-type">Mirror</span>.<span class="hl-fn">of</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>).<span class="hl-fn">field</span>(<span class="hl-str">&quot;name&quot;</span>);
<span class="hl-cmt">// unwrap the optional</span>
<span class="hl-type">MirrorField</span> <span class="hl-var">field</span> = <span class="hl-var">optional</span>.<span class="hl-fn">get</span>();
<span class="hl-cmt">// get the value of the field</span>
<span class="hl-cmt">// we pass null as the instance because the field is static</span>
<span class="hl-var">field</span>.<span class="hl-fn">get</span>(<span class="hl-const">null</span>); <span class="hl-cmt">// &quot;Mirror&quot;</span>
</code></pre><h3 id="setting-fields"><a href="#setting-fields" class="header-anchor" aria-hidden="true" role="presentation">###</a> Setting Fields</h3><pre class="highlight" data-lang="java"><code><span class="hl-cmt">// get the field</span>
<span class="hl-type">Optional</span>&lt;<span class="hl-type">MirrorField</span>&gt; <span class="hl-var">optional</span> = <span class="hl-type">Mirror</span>.<span class="hl-fn">of</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>).<span class="hl-fn">field</span>(<span class="hl-str">&quot;author&quot;</span>);
<span class="hl-cmt">// unwrap the optional</span>
<span class="hl-type">MirrorField</span> <span class="hl-var">field</span> = <span class="hl-var">optional</span>.<span class="hl-fn">get</span>();
<span class="hl-cmt">// set the value of the field</span>
<span class="hl-cmt">// we once again pass null as the instance because the field is static</span>
<span class="hl-var">field</span>.<span class="hl-fn">set</span>(<span class="hl-const">null</span>, <span class="hl-str">&quot;Shadowfacts&quot;</span>);
</code></pre><h3 id="invoking-methods"><a href="#invoking-methods" class="header-anchor" aria-hidden="true" role="presentation">###</a> Invoking Methods</h3><pre class="highlight" data-lang="java"><code><span class="hl-cmt">// get the method using the name and the types of the arguments it accepts</span>
<span class="hl-type">Optional</span>&lt;<span class="hl-type">MirrorMethod</span>&gt; <span class="hl-var">optional</span> = <span class="hl-type">Mirror</span>.<span class="hl-fn">of</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>).<span class="hl-fn">method</span>(<span class="hl-str">&quot;reverse&quot;</span>, <span class="hl-type">String</span>.<span class="hl-var">class</span>);
<span class="hl-cmt">// unwrap the optional</span>
<span class="hl-type">MirrorMethod</span> <span class="hl-var">method</span> = <span class="hl-var">optional</span>.<span class="hl-fn">get</span>();
<span class="hl-cmt">// invoke the method</span>
<span class="hl-var">method</span>.<span class="hl-fn">invoke</span>(<span class="hl-const">null</span>, <span class="hl-str">&quot;Mirror&quot;</span>); <span class="hl-cmt">// &quot;rorriM&quot;;</span>
</code></pre><h3 id="class-streams"><a href="#class-streams" class="header-anchor" aria-hidden="true" role="presentation">###</a> Class Streams</h3><pre class="highlight" data-lang="java"><code><span class="hl-type">Mirror</span>.<span class="hl-fn">ofAllUnwrapped</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>, <span class="hl-type">Test2</span>.<span class="hl-var">class</span>) <span class="hl-cmt">// create the stream of classes</span>
	.<span class="hl-fn">unwrap</span>() <span class="hl-cmt">// map the MirrorClasses to their Java versions</span>
	.<span class="hl-fn">toArray</span>(); <span class="hl-cmt">// [Test.class, Test2.class]</span>
</code></pre><h3 id="field-streams"><a href="#field-streams" class="header-anchor" aria-hidden="true" role="presentation">###</a> Field Streams</h3><pre class="highlight" data-lang="java"><code><span class="hl-type">Mirror</span>.<span class="hl-fn">ofAllUnwrapped</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>, <span class="hl-type">Test2</span>.<span class="hl-var">class</span>) <span class="hl-cmt">// create the stream of classes</span>
	.<span class="hl-fn">flatMapToFields</span>() <span class="hl-cmt">// flat map the classes to their fields</span>
	.<span class="hl-fn">get</span>(<span class="hl-const">null</span>) <span class="hl-cmt">// get the value of the fields on null</span>
	.<span class="hl-fn">toArray</span>(); <span class="hl-cmt">// [&quot;Mirror&quot;, &quot;Shadowfacts&quot;, &quot;Tesst 2&quot;]</span>
</code></pre><h3 id="method-streams"><a href="#method-streams" class="header-anchor" aria-hidden="true" role="presentation">###</a> Method Streams</h3><pre class="highlight" data-lang="java"><code><span class="hl-type">Mirror</span>.<span class="hl-fn">ofAllUnwrapped</span>(<span class="hl-type">Test</span>.<span class="hl-var">class</span>, <span class="hl-type">Test2</span>.<span class="hl-var">class</span>)  <span class="hl-cmt">// create the stream of classes</span>
	.<span class="hl-fn">flatMapToMethods</span>() <span class="hl-cmt">// flat map the classes to their methods</span>
	.<span class="hl-fn">filter</span>(<span class="hl-var">m</span> -&gt; <span class="hl-type">Arrays</span>.<span class="hl-fn">equals</span>(<span class="hl-var">m</span>.<span class="hl-fn">parameterTypes</span>(), <span class="hl-kw">new</span> <span class="hl-type">MirrorClass</span>&lt;?&gt;[]{<span class="hl-type">Mirror</span>.<span class="hl-fn">of</span>(<span class="hl-type">String</span>.<span class="hl-var">class</span>)})) <span class="hl-cmt">// filter the methods by which accept only a String</span>
	.<span class="hl-fn">invoke</span>(<span class="hl-const">null</span>, <span class="hl-str">&quot;Shadowfacts&quot;</span>) <span class="hl-cmt">// invoke them all on nothing, passing in &quot;Shadowfacts&quot;</span>
	.<span class="hl-fn">toArray</span>(); <span class="hl-cmt">// [&quot;stcafwodahS&quot;]</span>
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>Forge Modding Tutorials for 1.10.2</title>
      <link>https://shadowfacts.net/2016/forge-modding-tutorials-for-1-10-2/</link>
      <category>minecraft</category>
      <guid>https://shadowfacts.net/2016/forge-modding-tutorials-for-1-10-2/</guid>
      <pubDate>Thu, 30 Jun 2016 14:35:00 +0000</pubDate>
      <content:encoded><![CDATA[<p>The Forge modding tutorials have all the been <a href="/tutorials/forge-modding-1102/" data-link="/tutorials/forge-modding-1102/">updated to MC 1.10.2</a> as has the <a href="https://github.com/shadowfacts/TutorialMod/" data-link="github.com/shadowfacts/TutorialMod/">GitHub repo</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Introducing RTFM</title>
      <link>https://shadowfacts.net/2016/introducing-rtfm/</link>
      <category>minecraft</category>
      <guid>https://shadowfacts.net/2016/introducing-rtfm/</guid>
      <pubDate>Wed, 29 Jun 2016 16:00:00 +0000</pubDate>
      <content:encoded><![CDATA[<p><a href="https://rtfm.shadowfacts.net/" data-link="rtfm.shadowfacts.net">RTFM</a> is the brand new website that will contain the documentation for all of my projects, currently it only contains documentation for MC mods. Like this website, it is <a href="https://github.com/shadowfacts/RTFM" data-link="github.com/shadowfacts/RTFM">hosted on GitHub</a> using GitHub pages.</p>
<!-- excerpt-end -->
<p><img src="https://imgs.xkcd.com/comics/rtfm.png" alt="XKCD #293 RTFM" /></p>
]]></content:encoded>
    </item>
    <item>
      <title>1.9.4 Porting Spree</title>
      <link>https://shadowfacts.net/2016/1-9-4-porting-spree/</link>
      <category>minecraft</category>
      <guid>https://shadowfacts.net/2016/1-9-4-porting-spree/</guid>
      <pubDate>Sat, 21 May 2016 21:47:18 +0000</pubDate>
      <content:encoded><![CDATA[<p>Now that Forge for 1.9.4 is <a href="http://files.minecraftforge.net/maven/net/minecraftforge/forge/index_1.9.4.html" data-link="files.minecraftforge.net/maven/net/minec…">out</a>, I’ve begun the log and arduous process of porting my mods to 1.9.4 (if by long and arduous, you mean short and trivial).</p>
<!-- excerpt-end -->
<div class="mod">
	<h3 class="mod-name"><a href="http://minecraft.curseforge.com/projects/shadowmc">ShadowMC</a></h3>
	<span class="mod-version"><a href="http://minecraft.curseforge.com/projects/shadowmc/files/2301829">3.3.0</a></span>
	<p class="mod-desc">
		The library mod required by all of my other mods.
	</p>
</div>
<div class="mod">
	<h3 class="mod-name"><a href="http://minecraft.curseforge.com/projects/sleeping-bag">Sleeping Bag</a></h3>
	<span class="mod-version"><a href="http://minecraft.curseforge.com/projects/sleeping-bag/files/2301830">1.2.0</a></span>
	<p class="mod-desc">
		Adds a simple sleeping bag item that is usable anywhere and doens't set your spawn which makes it quite handy for bringing on adventures.
	</p>
</div>
<div class="mod">
	<h3 class="mod-name"><a href="http://minecraft.curseforge.com/projects/ye-olde-tanks">Ye Olde Tanks</a></h3>
	<span class="mod-version"><a href="http://minecraft.curseforge.com/projects/ye-olde-tanks/files/2301852">1.7.0</a></span>
	<p class="mod-desc">
		Fluid stuff: Fluid barrels, creative fluid barrels, fluid barrel minecarts, infinite water buckets.
	</p>
</div>
<div class="mod">
	<h3 class="mod-name"><a href="http://minecraft.curseforge.com/projects/discordchat">DiscordChat</a></h3>
	<span class="mod-version"><a href="http://minecraft.curseforge.com/projects/discordchat/files/2301839">1.2.0</a></span>
	<p class="mod-desc">
		Merges a Discord channel with Minecraft chat, primarily intended for servers.
	</p>
</div>
<div class="mod">
	<h3 class="mod-name"><a href="http://minecraft.curseforge.com/projects/shadowtweaks">ShadowTweaks</a></h3>
	<span class="mod-version"><a href="http://minecraft.curseforge.com/projects/shadowtweaks/files/2302146">1.9.0</a></span>
	<p class="mod-desc">A little tweaks mod with a variety of client/server tweaks.</p>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>Hello, World!</title>
      <link>https://shadowfacts.net/2016/hello-world/</link>
      <category>meta</category>
      <guid>https://shadowfacts.net/2016/hello-world/</guid>
      <pubDate>Fri, 06 May 2016 15:13:18 +0000</pubDate>
      <content:encoded><![CDATA[<p>Hello again, world! Welcome to the third iteration of my website. Originally my site was hosted on GitHub pages and only available at <a href="https://shadowfacts.github.io" data-link="shadowfacts.github.io">shadowfacts.github.io</a>. I wrote a couple of tutorials on using <a href="http://minecraftforge.net" data-link="minecraftforge.net">Forge</a> to mod 1.6.4, but never really finished anything other than super basic setup/recipes. Later, after I got <a href="https://shadowfacts.net" data-link="shadowfacts.net">shadowfacts.net</a>, I decided to set up a propper website using <a href="https://wordpress.org" data-link="wordpress.org">WordPress</a>. I copied over all of the old tutorials from my old GitHub pages site, but never really did anything else with it. After my website being offline for almost a year, I’ve finally decided to switch back to GitHub for the simplicity (also I &lt;3 <a href="https://jekyllrb.com" data-link="jekyllrb.com">Jekyll</a>). Using Jekyll, I’ve got a structure in place that I can use to easily publish tutorials in a structured format. There is one tutorial series that I’m currently writing and that is <a href="/tutorials/forge-modding-19/" data-link="/tutorials/forge-modding-19/">Forge Mods in 1.9</a>, and hopefully more series will follow.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>