<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <title>Ryan Martin&#39;s Blog</title>
  <subtitle></subtitle>
  <link href="https://ryanmartin.me/feed.xml" rel="self" />
  <link href="https://ryanmartin.me/" />
  <updated>2026-04-04T12:25:25Z</updated>
  <id>https://ryanmartin.me/</id>
  <author>
    <name>Ryan Martin</name>
    <email>hi@ryanmartin.me</email>
  </author>
  <entry>
    <title>Understanding Recursive Ascent Parsers</title>
    <link href="https://ryanmartin.me/articles/recursive-ascent/" />
    <updated>2026-04-04T12:25:25Z</updated>
    <id>https://ryanmartin.me/articles/recursive-ascent/</id>
    <content type="html">
&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;Recursive ascent is an obscure parsing technique I researched about a few years ago. It’s an interesting approach to bottom-up parsing, the parsing method used by parser generators like &lt;a href=&quot;https://www.gnu.org/software/bison/&quot;&gt;Bison&lt;/a&gt; or &lt;a href=&quot;https://www.antlr.org/&quot;&gt;ANTLR&lt;/a&gt;. If you’re familiar with recursive descent parsing, recursive ascent parsing is the bottom-up version of it.&lt;/p&gt;
&lt;aside id=&quot;sn1&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;1.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;This &lt;a href=&quot;https://www.abubalay.com/blog/2018/04/08/recursive-ascent&quot;&gt;blog post&lt;/a&gt; by Russel Johnston about implementing a JSON parser in Rust using recursive ascent really helped me when I first started. It&#39;s probably the only blog post about recursive ascent on the internet besides this one. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;
&lt;aside id=&quot;sn2&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;2.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Here are &lt;a href=&quot;https://dl.acm.org/doi/pdf/10.1145/47907.47909&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://3e8.org/pub/scheme/doc/CPS%20Recursive%20Ascent%20Parsing.pdf&quot;&gt;papers&lt;/a&gt; &lt;a href=&quot;https://ceur-ws.org/Vol-2951/paper2.pdf&quot;&gt;about recursive ascent parsers&lt;/a&gt; that you could read if you&#39;re interested in the theory. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Unfortunately, there isn’t a lot of material about it on the internet. When I first started learning about it, there was only one blog post about it.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn1&quot; id=&quot;snref1&quot;&gt;[1]&lt;/a&gt;
        &lt;/sup&gt; There were also some academic papers about it, but these are very theory-heavy and difficult to read (at least for me).&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn2&quot; id=&quot;snref2&quot;&gt;[2]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;aside id=&quot;sn3&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;3.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://craftinginterpreters.com/&quot;&gt;Crafting Interpreters&lt;/a&gt; is a really good introductory book on writing compilers, and its section on parsing is all about implementing a recursive descent parser. It&#39;s also probably the most influential book in my programming journey. I learned a lot from it, which I applied in areas other than in writing compilers. Highly recommend reading it if you&#39;re in software engineering. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I’m writing this post to share what I’ve learned about recursive ascent parsing; how it works and how to implement it. I’ll assume you have some familiarity with recursive descent parsing, as it’s usually the first parsing technique people learn. If not, I recommend reading up on it first.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn3&quot; id=&quot;snref3&quot;&gt;[3]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The first half of this post will cover the theory behind bottom-up parsing, and the second half will delve into the code implementation of a recursive ascent parser.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: I’m not an expert. I didn’t take any classes on compiler theory, and I haven’t read &lt;a href=&quot;https://suif.stanford.edu/dragonbook/&quot;&gt;the Dragon Book&lt;/a&gt;. Please &lt;a href=&quot;mailto:hi@ryanmartin.me&quot;&gt;reach out&lt;/a&gt; If you notice any mistakes in this post, especially in the first half!&lt;/p&gt;
&lt;aside class=&quot;toc&quot;&gt;&lt;div&gt;
      &lt;h2 id=&quot;toc&quot;&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#toc&quot; class=&quot;header-anchor&quot;&gt;Table of Contents&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#bottom-up-vs-top-down-parsing&quot;&gt;Bottom-Up vs Top-Down Parsing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#lr-algorithm&quot;&gt;LR Algorithm&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#building-the-state-machine&quot;&gt;Building the State Machine&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#the-action-and-goto-table&quot;&gt;The Action and Goto Table&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#writing-the-parser&quot;&gt;Writing The Parser&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#a-simple-lexer&quot;&gt;A Simple Lexer&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#representing-the-abstract-syntax-tree-(ast)&quot;&gt;Representing the Abstract Syntax Tree (AST)&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#converting-the-parse-table-into-functions&quot;&gt;Converting the Parse Table into Functions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#interpreting-the-ast&quot;&gt;Interpreting the AST&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#end&quot;&gt;End&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;bottom-up-vs-top-down-parsing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#bottom-up-vs-top-down-parsing&quot;&gt;Bottom-Up vs Top-Down Parsing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Parsing techniques can be roughly grouped into 2 types: &lt;a href=&quot;https://en.wikipedia.org/wiki/Top-down_parsing&quot;&gt;top-down&lt;/a&gt; and &lt;a href=&quot;https://en.wikipedia.org/wiki/Bottom-up_parsing&quot;&gt;bottom-up&lt;/a&gt;. Top-down parsers start from the highest level of the grammar rules, while bottom-up parsers start from the lowest level. Generally, top-down parsing algorithms are easier to understand and implement, but are less powerful and accept a smaller set of grammars than bottom-up algorithms.&lt;/p&gt;
&lt;aside id=&quot;sn4&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;4.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I found this analogy from &lt;a href=&quot;https://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html&quot;&gt;this blog post&lt;/a&gt; by Josh Haberman. He went deep into the details of both LL and LR algorithms in his blog post while keeping it easy to read. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;A nice way to visualise the difference between top-down and bottom-up approaches, which I just found recently, is to correspond top-down parsing with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Polish_notation&quot;&gt;Polish notation&lt;/a&gt;, and bottom-up parsing with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Reverse_Polish_notation&quot;&gt;reversed Polish notation&lt;/a&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn4&quot; id=&quot;snref4&quot;&gt;[4]&lt;/a&gt;
        &lt;/sup&gt; In Polish notation, the operators come before the operands. In reversed Polish notation, it’s the opposite; the operands come before the operators.&lt;/p&gt;
&lt;p&gt;Let’s use this math expression &lt;code&gt;1 + 2 * 3&lt;/code&gt; as an example. We can also represent this expression as a tree:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/recursive-ascent/7p5d1zaWUD-2146.webp&quot; alt=&quot;Math expression as tree diagram&quot; width=&quot;2146&quot; height=&quot;683&quot;&gt;&lt;/p&gt;
&lt;p&gt;A top-down parser builds the parse tree starting from the root node and ending in the leaf nodes. In computer science lingo, this is also called a pre-order traversal. If you write down the nodes in the order it is traversed, you’ll end up with &lt;code&gt;+ 1 * 2 3&lt;/code&gt;, the Polish notation of the previous expression.&lt;/p&gt;
&lt;p&gt;On the other hand, a bottom-up parser builds the parse tree starting from the leaf nodes up to the root node, aka, post-order traversal. The order the nodes are traversed will form the reversed Polish notation of the original expression: &lt;code&gt;2 3 * 1 +&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You’re probably already familiar with the top-down approach, as that is how recursive descent parsers work. Personally, I find it difficult to reverse this logic and understand how a bottom-up approach works. Unlike top-down algorithms, which determine what to do next based on the current grammar rule, how does a bottom-up algorithm know what rule it is in and what it should do next?&lt;/p&gt;
&lt;h2 id=&quot;lr-algorithm&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#lr-algorithm&quot;&gt;LR Algorithm&lt;/a&gt;&lt;/h2&gt;
&lt;aside id=&quot;sn5&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;5.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Here&#39;s a &lt;a href=&quot;https://dev.to/stereobooster/an-overview-of-parsing-algorithms-3lo2&quot;&gt;nice overview of parsing algorithm literature&lt;/a&gt; you can explore if you are interested in the different variants of LR. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser&quot;&gt;LR&lt;/a&gt; is a bottom-up parsing algorithm used in most parser generators. More accurately, it is a family of parsing algorithms that follow a similar approach. Recursive ascent parsers also use this algorithm. There are many variants, such as LR(k), SLR, LALR, etc.,&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn5&quot; id=&quot;snref5&quot;&gt;[5]&lt;/a&gt;
        &lt;/sup&gt; but all of them are based on the same two fundamental actions: &lt;strong&gt;shift&lt;/strong&gt; and &lt;strong&gt;reduce&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Shift means to advance or “shift” to the next token from the input stream. If the shifted tokens match one of the rules of the grammar, they will be converted or “reduced” into that rule. An LR parser will continue shifting and reducing until the entire input is consumed and transformed into a single parse tree. This concept may sound abstract, so let’s use an example to illustrate it.&lt;/p&gt;
&lt;p&gt;We’ll use this grammar:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;expr -&amp;gt; expr &#39;+&#39; term
expr -&amp;gt; expr &#39;-&#39; term
expr -&amp;gt; term
term -&amp;gt; INTEGER&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This grammar defines a language that consists only of additions and subtractions of integers. The table below shows the actions an LR parser takes when parsing the input &lt;code&gt;1 + 2 - 3&lt;/code&gt; according to the grammar above:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parse stack&lt;/th&gt;
&lt;th&gt;Unparsed&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;empty&lt;/td&gt;
&lt;td&gt;1 + 2 - 3&lt;/td&gt;
&lt;td&gt;Shift 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;+ 2 - 3&lt;/td&gt;
&lt;td&gt;Reduce term -&amp;gt; INTEGER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;term&lt;/td&gt;
&lt;td&gt;+ 2 - 3&lt;/td&gt;
&lt;td&gt;Reduce expr -&amp;gt; term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr&lt;/td&gt;
&lt;td&gt;+ 2 - 3&lt;/td&gt;
&lt;td&gt;Shift +&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr +&lt;/td&gt;
&lt;td&gt;2 - 3&lt;/td&gt;
&lt;td&gt;Shift 2 (because there are no rules that match expr ‘+’)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr + 2&lt;/td&gt;
&lt;td&gt;- 3&lt;/td&gt;
&lt;td&gt;Reduce term -&amp;gt; INTEGER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr + term&lt;/td&gt;
&lt;td&gt;- 3&lt;/td&gt;
&lt;td&gt;Reduce expr -&amp;gt; expr ‘+’ term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr&lt;/td&gt;
&lt;td&gt;- 3&lt;/td&gt;
&lt;td&gt;Shift -&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr -&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Shift 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr - 3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Reduce term -&amp;gt; INTEGER&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr - term&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Reduce expr -&amp;gt; expr - term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expr&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Accept&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Here, I used the term “parse stack” for the shifted inputs. That’s because most LR parsers use a stack to keep track of the shifted tokens. A shift is a push to the stack, and a reduce pops tokens that match a rule and pushes the corresponding rule to the stack.&lt;/p&gt;
&lt;p&gt;How does the parser know which rule it should reduce to? Most LR parsers construct a &lt;strong&gt;parse table&lt;/strong&gt;, which is a table of parser instructions based on the grammar. Entries in the table tell the parser whether to shift or reduce based on the current token in the input stream. The entire combination of actions a parser can take based on any input fits in this one table. This is possible because there are only a finite number of possible states an LR parser can assume for a given grammar. These are called the &lt;strong&gt;LR states&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Because the number of states is finite, an LR parser is essentially just a &lt;a href=&quot;https://en.wikipedia.org/wiki/Finite-state_machine&quot;&gt;finite state machine (FSM)&lt;/a&gt; modelled after the LR states. The parse table contains the state transitions. The parse stack is used to keep track of the current state. In fact, parser generators are just programs that convert a grammar into a table-based state machine.&lt;/p&gt;
&lt;p&gt;What about recursive ascent parsers then? A recursive ascent parser is just the “functional” version of a traditional LR table-based state machine. Instead of rows in a table, state transitions are encoded as functions. A function call is a shift action, and a reduce action happens when the function returns. The parse stack is the function call stack!!&lt;/p&gt;
&lt;p&gt;I had an Aha! moment when I first realised this about recursive ascent parsers. This might not make sense now, but once we get to the code implementation, it will hopefully make this more clear.&lt;/p&gt;
&lt;h2 id=&quot;building-the-state-machine&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#building-the-state-machine&quot;&gt;Building the State Machine&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before getting into the code, first, we’ll need to understand how to generate a parse table. This is a process that is automated by parser generators, but in recursive ascent, we have to do this manually. The first step is to compute all the possible LR states for a given grammar, in other words, build a finite state machine. Again, we’ll use an example grammar to illustrate this.&lt;/p&gt;
&lt;aside id=&quot;sn6&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;6.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;This is done to show that the parser can handle operator precedence based on the grammar rules. Subtraction is removed to keep the section short and readable. The number of possible states will grow exponentially the more complex your grammar is. This is why it is inconvenient to write a bottom-up parser by hand, and why people create parser generators. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;We’ll modify the grammar from the previous section to support multiplications and drop subtractions:&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn6&quot; id=&quot;snref6&quot;&gt;[6]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;S -&amp;gt; expr eof
expr -&amp;gt; expr &#39;+&#39; term
expr -&amp;gt; term
term -&amp;gt; term &#39;*&#39; factor
term -&amp;gt; factor
factor -&amp;gt; INTEGER&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the extra rule &lt;code&gt;S&lt;/code&gt; (start), which will be used for a reduction when the parser has accepted the whole input. &lt;code&gt;eof&lt;/code&gt; indicates the end of the input stream.&lt;/p&gt;
&lt;aside id=&quot;sn7&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;7.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;A grammar rule is often called a &quot;production&quot; in academic literature. I just use the term &quot;grammar rules&quot; because it&#39;s more intuitive. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;
&lt;aside id=&quot;sn8&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;8.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;My explanation of the algorithm for creating the parse table might not click with you, so here are &lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser#Constructing_LR(0)_parsing_tables&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://suif.stanford.edu/dragonbook/lecture-notes/Stanford-CS143/08-Bottom-Up-Parsing.pdf&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;https://chatgpt.com&quot;&gt;resources&lt;/a&gt; you can read if you&#39;re still confused. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Next, we’ll need to construct the &lt;strong&gt;LR(0) items&lt;/strong&gt;, which are grammar rules with a dot (•) to mark the current position of the parser.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn7&quot; id=&quot;snref7&quot;&gt;[7]&lt;/a&gt;
        &lt;/sup&gt; E.g. the rule &lt;code&gt;S -&amp;gt; expr eof&lt;/code&gt; contains one LR(0) item: &lt;code&gt;S -&amp;gt; • expr eof&lt;/code&gt;. Everything to the left of the dot has already been parsed or shifted.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn8&quot; id=&quot;snref8&quot;&gt;[8]&lt;/a&gt;
        &lt;/sup&gt; For brevity’s sake, I’ll shorten “LR(0) item” to just “item” going forward.&lt;/p&gt;
&lt;p&gt;For each item, we then need to compute its &lt;strong&gt;closure&lt;/strong&gt;. The closure of an item is the set of all possible items that can be derived from applying the appropriate grammar rules to it. Here’s how it works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start with a set of items. For our grammar, this will be &lt;code&gt;{ S -&amp;gt; • expr eof }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For each item, if the symbol on the right of the dot is a non-terminal, expand its rules based on the grammar and turn the expanded rules into items by adding a dot. From our first item, we expand &lt;code&gt;expr&lt;/code&gt; into &lt;code&gt;expr -&amp;gt; • expr &#39;+&#39; term&lt;/code&gt; and &lt;code&gt;expr -&amp;gt; • term&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Add the expanded items to the original set. Our set should now look like &lt;code&gt;{ S -&amp;gt; • expr eof, expr -&amp;gt; • expr &#39;+&#39; term, expr -&amp;gt; • term }&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Repeat steps 2 and 3 until no new items can be added to the set.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A non-terminal is any symbol that represents a grammar rule, e.g. &lt;code&gt;expr&lt;/code&gt;, &lt;code&gt;term&lt;/code&gt;, or &lt;code&gt;factor&lt;/code&gt;. The opposite of this is a terminal, which is an actual value or token that is produced by the lexer, e.g. &lt;code&gt;&#39;+&#39;&lt;/code&gt;, &lt;code&gt;&#39;-&#39;&lt;/code&gt;, and &lt;code&gt;INTEGER&lt;/code&gt;. If we apply closure to the first item in the grammar &lt;code&gt;S -&amp;gt; • expr eof&lt;/code&gt;, we’ll end up with the first LR state:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 0
S -&amp;gt; • expr eof
expr -&amp;gt; • expr &#39;+&#39; term
expr -&amp;gt; • term
term -&amp;gt; • term &#39;*&#39; factor
term -&amp;gt; • factor
factor -&amp;gt; • INTEGER&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can then transition from this state into the other states by accepting different values and advancing the dot. The values that the parser can accept are the terminals and non-terminals on the right side of the dot. So, for state 0, the values it can accept are &lt;code&gt;expr&lt;/code&gt;, &lt;code&gt;term&lt;/code&gt;, &lt;code&gt;factor&lt;/code&gt;, and &lt;code&gt;INTEGER&lt;/code&gt;. Let’s say the parser receives an &lt;code&gt;expr&lt;/code&gt;. This will become our second state:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 1 (State 0 + expr)
S -&amp;gt; expr • eof (Accept)
expr -&amp;gt; expr • &#39;+&#39; term&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the dot reaches the end of the starting rule, it is called the accept state. It means that the input has been parsed fully and the parsing is complete. If we had instead received a &lt;code&gt;term&lt;/code&gt; in the first state, then we’d end up with this state:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 2 (State 0 + term)
expr -&amp;gt; term •
term -&amp;gt; term • &#39;*&#39; factor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we just repeat this process until all dots are at the end of each rule. There is an exception to this though, which I’ll get to in a bit. Anyways, here are some more states:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 3 (State 0 + factor)
term -&amp;gt; factor •

# State 4 (State 0 + INTEGER)
factor -&amp;gt; INTEGER •

# State 5 (State 1 + &#39;+&#39;)
expr -&amp;gt; expr &#39;+&#39; • term
term -&amp;gt; • term &#39;*&#39; factor
term -&amp;gt; • factor
factor -&amp;gt; • INTEGER

# State 6 (State 2 + &#39;*&#39;)
term -&amp;gt; term &#39;*&#39; • factor
factor -&amp;gt; • INTEGER

# State 7 (State 5 + term)
expr -&amp;gt; expr &#39;+&#39; term •
term -&amp;gt; term • &#39;*&#39; factor&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now, it might make sense to do this for the next state:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 8 (State 1 + factor)
term -&amp;gt; factor •&lt;/code&gt;&lt;/pre&gt;
&lt;aside id=&quot;sn9&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;9.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Note that the order of the states doesn&#39;t matter. The parser will still work if the states were created in a different order, e.g. state 1 = state 0 + &lt;code&gt;term&lt;/code&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Notice that this is the same state as state 3. If we encounter a state that we have seen before, we don’t have to create a new state. Instead, we’ll just jump or &lt;strong&gt;goto&lt;/strong&gt; the state that we’ve already found before. This will make more sense when we build the actual parse table. Now, let’s finish the last state.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn9&quot; id=&quot;fnref9&quot;&gt;[9]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn9&quot; id=&quot;snref9&quot;&gt;[9]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;# State 8 (State 6 + factor)
term -&amp;gt; term &#39;*&#39; factor •&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we’re done! Here’s a diagram to help you visualise the state transitions:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/recursive-ascent/reip9qNHGK-3085.webp&quot; alt=&quot;State transition diagram&quot; width=&quot;3085&quot; height=&quot;1272&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-action-and-goto-table&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#the-action-and-goto-table&quot;&gt;The Action and Goto Table&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have the state machine, we can translate it into the parse table. A transition between states represents a shift action. Items with a dot at the end represent reduce actions. The parse table consists of two parts, the action table and the goto table. The action table tells the parser whether to shift or to reduce based on a lookahead terminal. The goto table tells the parser which state to go to, given a non-terminal. Here’s what the action and goto table looks like for our grammar:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;Accept&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt7&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 7&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 8&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;Each row represents a state in the state machine, each column in the action table represents a terminal, and each column in the goto table represents a non-terminal. &lt;strong&gt;sN&lt;/strong&gt; means “shift and go to state N”, &lt;strong&gt;rN&lt;/strong&gt; means “reduce using rule N”, and &lt;strong&gt;gtN&lt;/strong&gt; means “go to state N”. Unlike shift, goto doesn’t take in a token from the input stream and just transitions to the next state.&lt;/p&gt;
&lt;p&gt;Here are the grammar rules again for reference, with the rule numbers added for reductions:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;1. S -&amp;gt; expr eof
2. expr -&amp;gt; expr &#39;+&#39; term
3. expr -&amp;gt; term
4. term -&amp;gt; term &#39;*&#39; factor
5. term -&amp;gt; factor
6. factor -&amp;gt; INTEGER&lt;/code&gt;&lt;/pre&gt;
&lt;aside id=&quot;sn10&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;10.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;You can read more about why this is on &lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser#Parse_table_for_the_example_grammar&quot;&gt;this Wikipedia page&lt;/a&gt;. It should be a good starting point to explore the different variants of LR and the types of parse conflicts. For this article, I intentionally chose a grammar that is accepted by LR(0) to keep it simple. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;The parse table is usually much larger than the grammar, and languages with a complex grammar will produce huge tables which are almost impossible to compute by hand. This is why people create parser generators. Based on how the states and parse table are generated, the resulting parser can be called either a simple LR (SLR), look-ahead LR (LALR), or a canonical LR parser (LR(k)). I’m not going to go into detail about these algorithms here, as it can lead to a rabbit hole. Just know that we have a working LR(0) parse table now.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn10&quot; id=&quot;fnref10&quot;&gt;[10]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn10&quot; id=&quot;snref10&quot;&gt;[10]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The next step of the LR algorithm is to run the actual parser using a real input. Traditional LR parsers will run a loop and parse a token stream following the rules of the parse table. In our case, we’ll implement this mechanism as mutually recursive functions.&lt;/p&gt;
&lt;h2 id=&quot;writing-the-parser&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#writing-the-parser&quot;&gt;Writing The Parser&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point, we’re ready to build the actual parser. I’ll be writing the code in Python because it’s easy to read. To make sure our parser is correct, we’ll also implement an interpreter for the expressions we parsed. First, let’s start with the program’s entry point:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; sys

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;usage: {} [STRING]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; __name__ &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;__main__&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    main&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This program will receive a string as an argument and print out the evaluated string. Don’t worry about the unimplemented functions for now. We’ll get to them soon.&lt;/p&gt;
&lt;h3 id=&quot;a-simple-lexer&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#a-simple-lexer&quot;&gt;A Simple Lexer&lt;/a&gt;&lt;/h3&gt;
&lt;aside id=&quot;sn11&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;11.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;You can read &lt;a href=&quot;https://craftinginterpreters.com/scanning.html&quot;&gt;Crafting Interpreters&lt;/a&gt; for a better idea of what production lexers do. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref11&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Since we don’t have a complex grammar, our lexer can just scan tokens separated by whitespace. This works, but it also means that we need to separate the terminals in our input string with spaces. A better lexer knows whether a token can be scanned regardless of whitespaces.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn11&quot; id=&quot;fnref11&quot;&gt;[11]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn11&quot; id=&quot;snref11&quot;&gt;[11]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;lex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;split&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 id=&quot;representing-the-abstract-syntax-tree-(ast)&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#representing-the-abstract-syntax-tree-(ast)&quot;&gt;Representing the Abstract Syntax Tree (AST)&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We’ll also need a type to represent the AST that will be produced by the parser:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;__init__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AST | None&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;AST | None&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; kind&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; left
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; right
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value
        self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; kind&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you’ve noticed, the &lt;code&gt;AST&lt;/code&gt; class above looks like a binary tree. That’s because all the expressions we have are binary operations, which can be represented as a binary tree. The final AST should look like &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#bottom-up-vs-top-down-parsing&quot;&gt;this diagram&lt;/a&gt; from an earlier section. For more complex grammars, you might need to use a more complex data structure for the AST.&lt;/p&gt;
&lt;h3 id=&quot;converting-the-parse-table-into-functions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#converting-the-parse-table-into-functions&quot;&gt;Converting the Parse Table into Functions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our &lt;code&gt;parse&lt;/code&gt; function will start from the first state. If you recall the previous parse table, there are 4 possible actions the parser can take:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 0&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;We can translate this to code like so:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# shift token, move dot forwards&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# go to state 4&lt;/span&gt;
        ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state4&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        panic&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;factor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# go to state 3&lt;/span&gt;
        ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state3&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;kind &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# go to state 2&lt;/span&gt;
        ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# go to state 1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state1&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state0&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Note, we don’t actually need to check the &lt;code&gt;ast.kind&lt;/code&gt; here because if &lt;code&gt;ast = state4(token)&lt;/code&gt; succeeds, then we’re guaranteed to get a &lt;code&gt;factor&lt;/code&gt;. The same goes for &lt;code&gt;ast = state3(ast)&lt;/code&gt;, which is guaranteed to return a &lt;code&gt;term&lt;/code&gt;. So, we can rewrite &lt;code&gt;state0&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# shift token, move dot forwards&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# go to state 4&lt;/span&gt;
        ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state4&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        panic&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# go to state 3&lt;/span&gt;
    ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state3&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# go to state 2&lt;/span&gt;
    ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state2&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# go to state 1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state1&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn12&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;12.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;This is a very basic approach to handling parse errors. Production-grade parsers will usually report errors, ignore them, and continue parsing. This is to catch as many errors as it can so that the programmer doesn&#39;t have to recompile to find more errors. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref12&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Here are the helper function definitions. &lt;code&gt;peek&lt;/code&gt; returns the next token, while &lt;code&gt;panic&lt;/code&gt; prints an error message if an unexpected token is found and terminates the program:&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn12&quot; id=&quot;fnref12&quot;&gt;[12]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn12&quot; id=&quot;snref12&quot;&gt;[12]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;peek&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unexpected token: {}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    exit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;As mentioned before, reduce happens when returning from a function:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# reduce using rule 5: term -&amp;gt; factor&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# reduce using rule 6: factor -&amp;gt; INTEGER&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;factor&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now here’s where things get more interesting:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 1&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;accept&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r3&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# loop to handle left recursion&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# shift token&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token comment&quot;&gt;# go to state 5&lt;/span&gt;
        ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state5&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# accept&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; ast

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state6&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# reduce using rule 3: expr -&amp;gt; term&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;expr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The grammar we used has rules that contain &lt;a href=&quot;https://en.wikipedia.org/wiki/Left_recursion&quot;&gt;left recursion&lt;/a&gt;. This happens when a rule starts with a symbol that is itself, e.g. &lt;code&gt;expr -&amp;gt; expr &#39;+&#39; term&lt;/code&gt; and &lt;code&gt;term -&amp;gt; term &#39;*&#39; factor&lt;/code&gt;. Top-down parsers can’t handle this type of grammar. If you’ve implemented a recursive descent parser before, a left-recursive rule will cause the parse function to immediately call itself before doing anything else, leading to infinite recursion and a stack overflow.&lt;/p&gt;
&lt;p&gt;When using top-down parsers, you have to refactor the grammar to be right-recursive or tail-recursive. LR parsers can handle left recursion just fine, so this isn’t a problem for us. We can use a loop and a condition to check whether the recursive calls are done.&lt;/p&gt;
&lt;p&gt;In state 1 and state 2, the parser has parsed the left side of the binary operation. In the next states, the parser will parse the right side of the binary operation:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 5&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt7&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;gt8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state4&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        panic&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state3&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state7&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state4&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        panic&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; state8&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Once the right side is parsed, we can then reduce these rules in state 7 and state 8:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Action&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;Goto&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;th style=&quot;text-align:center&quot;&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;+&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;*&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;INTEGER&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;eof&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;expr&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;term&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 7&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;s6&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r2&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State 8&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;r4&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;td style=&quot;text-align:center&quot;&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state7&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;while&lt;/span&gt; peek&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        right &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; state6&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# reduce using rule 2: expr -&amp;gt; expr &#39;+&#39; term&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;expr&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; tokens

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;state8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# reduce using rule 4: term -&amp;gt; term &#39;*&#39; factor&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; AST&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; right&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;term&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;And that’s all of the code for the parser! Now we just have to check whether the output is correct. Let’s add a method in the &lt;code&gt;AST&lt;/code&gt; class to pretty-print itself, so that it’s easier to visualise:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;pprint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;( {} &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; {} &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pprint&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pprint&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;not&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;isdigit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot; )&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; end&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn13&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;13.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;What better way to visualise AST than with s-expressions? When you read a lisp program, you&#39;re looking directly at the program&#39;s AST, which is just a lisp data structure! This is what clojurists often mean when they say &quot;It&#39;s just data&quot;. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#snref13&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I’m printing the AST as s-expressions so that it’s easy to see the expressions follow the correct operator precedence.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fn13&quot; id=&quot;fnref13&quot;&gt;[13]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#sn13&quot; id=&quot;snref13&quot;&gt;[13]&lt;/a&gt;
        &lt;/sup&gt; Given an input like &lt;code&gt;1 + 2 * 3 + 4&lt;/code&gt;, this method will print out the following: &lt;code&gt;( + ( +  1 ( *  2  3  ) ) 4  )&lt;/code&gt;. This looks right. All that’s left now is to implement the &lt;code&gt;evaluate&lt;/code&gt; function and test if it works as expected.&lt;/p&gt;
&lt;h3 id=&quot;interpreting-the-ast&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#interpreting-the-ast&quot;&gt;Interpreting the AST&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here’s the &lt;code&gt;evaluate&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;evaluate&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;elif&lt;/span&gt; ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;It will recursively evaluate all the nodes in the AST. Let’s run the program and check if the output is correct:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;python3 recursive-ascent.py &lt;span class=&quot;token string&quot;&gt;&#39;1 + 2 * 3 + 4&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# 11&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Good, it works! Now, let’s write some generated tests to have more confidence in the program. Here’s a function that would generate random numbers and combinations of operators (&lt;code&gt;+&lt;/code&gt; and &lt;code&gt;*&lt;/code&gt;), run the &lt;code&gt;evaluate&lt;/code&gt; function on them, and compare the result to Python’s &lt;code&gt;eval&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;recursive-ascent.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;tests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    errors &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        k &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;randint&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        nums &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;str&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sample&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;10_000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; k&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        ops &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;choice&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;+&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; _ &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;k &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        expr &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; j &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;zip&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;nums&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; j&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;nums&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;lex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        py_res &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; res &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; py_res&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;incorrect output for:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;got:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;expected:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; py_res&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            errors &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; errors &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;all tests passed!&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# also some changes to main to allow users to run the tests&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;usage: {} [STRING]&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
        tests&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
    tokens &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lex&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;input&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    ast &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; evaluate&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ast&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;eval&lt;/code&gt; function will run any Python expression. Since Python can also evaluate math expressions, we can use it as a reference to verify the correctness of our &lt;code&gt;evaluate&lt;/code&gt; function. If you run &lt;code&gt;python3 main.py test&lt;/code&gt;, you should get this output: &lt;code&gt;all tests passed!&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;end&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/recursive-ascent/#end&quot;&gt;End&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;And that’s all. You can find the complete code on &lt;a href=&quot;https://github.com/rmrt1n/rmrt1n.github.io/tree/main/code/recursive-ascent&quot;&gt;this GitHub repo&lt;/a&gt;. Hopefully, you have a good idea of how LR and recursive ascent parsers work now. You probably won’t ever use this technique when building a real compiler, but at least you learned something new. If you’d like to learn more about this topic or explore other parsing techniques, check out the links in the footnotes.&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep asterism&quot;&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This &lt;a href=&quot;https://www.abubalay.com/blog/2018/04/08/recursive-ascent&quot;&gt;blog post&lt;/a&gt; by Russel Johnston about implementing a JSON parser in Rust using recursive ascent really helped me when I first started. It’s probably the only blog post about recursive ascent on the internet besides this one. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Here are &lt;a href=&quot;https://dl.acm.org/doi/pdf/10.1145/47907.47909&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://3e8.org/pub/scheme/doc/CPS%20Recursive%20Ascent%20Parsing.pdf&quot;&gt;papers&lt;/a&gt; &lt;a href=&quot;https://ceur-ws.org/Vol-2951/paper2.pdf&quot;&gt;about recursive ascent parsers&lt;/a&gt; that you could read if you’re interested in the theory. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://craftinginterpreters.com/&quot;&gt;Crafting Interpreters&lt;/a&gt; is a really good introductory book on writing compilers, and its section on parsing is all about implementing a recursive descent parser. It’s also probably the most influential book in my programming journey. I learned a lot from it, which I applied in areas other than in writing compilers. Highly recommend reading it if you’re in software engineering. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I found this analogy from &lt;a href=&quot;https://blog.reverberate.org/2013/07/ll-and-lr-parsing-demystified.html&quot;&gt;this blog post&lt;/a&gt; by Josh Haberman. He went deep into the details of both LL and LR algorithms in his blog post while keeping it easy to read. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Here’s a &lt;a href=&quot;https://dev.to/stereobooster/an-overview-of-parsing-algorithms-3lo2&quot;&gt;nice overview of parsing algorithm literature&lt;/a&gt; you can explore if you are interested in the different variants of LR. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This is done to show that the parser can handle operator precedence based on the grammar rules. Subtraction is removed to keep the section short and readable. The number of possible states will grow exponentially the more complex your grammar is. This is why it is inconvenient to write a bottom-up parser by hand, and why people create parser generators. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;A grammar rule is often called a “production” in academic literature. I just use the term “grammar rules” because it’s more intuitive. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;My explanation of the algorithm for creating the parse table might not click with you, so here are &lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser#Constructing_LR(0)_parsing_tables&quot;&gt;some&lt;/a&gt; &lt;a href=&quot;https://suif.stanford.edu/dragonbook/lecture-notes/Stanford-CS143/08-Bottom-Up-Parsing.pdf&quot;&gt;other&lt;/a&gt; &lt;a href=&quot;https://chatgpt.com&quot;&gt;resources&lt;/a&gt; you can read if you’re still confused. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn9&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Note that the order of the states doesn’t matter. The parser will still work if the states were created in a different order, e.g. state 1 = state 0 + &lt;code&gt;term&lt;/code&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn10&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You can read more about why this is on &lt;a href=&quot;https://en.wikipedia.org/wiki/LR_parser#Parse_table_for_the_example_grammar&quot;&gt;this Wikipedia page&lt;/a&gt;. It should be a good starting point to explore the different variants of LR and the types of parse conflicts. For this article, I intentionally chose a grammar that is accepted by LR(0) to keep it simple. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn11&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You can read &lt;a href=&quot;https://craftinginterpreters.com/scanning.html&quot;&gt;Crafting Interpreters&lt;/a&gt; for a better idea of what production lexers do. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref11&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn12&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This is a very basic approach to handling parse errors. Production-grade parsers will usually report errors, ignore them, and continue parsing. This is to catch as many errors as it can so that the programmer doesn’t have to recompile to find more errors. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref12&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn13&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;What better way to visualise AST than with s-expressions? When you read a lisp program, you’re looking directly at the program’s AST, which is just a lisp data structure! This is what clojurists often mean when they say “It’s just data”. &lt;a href=&quot;https://ryanmartin.me/articles/recursive-ascent/#fnref13&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Rewriting My Tests With Generative Testing</title>
    <link href="https://ryanmartin.me/articles/generative-testing/" />
    <updated>2026-04-04T12:25:25Z</updated>
    <id>https://ryanmartin.me/articles/generative-testing/</id>
    <content type="html">
&lt;html&gt;&lt;head&gt;&lt;style id=&quot;MJX-SVG-styles&quot;&gt;
mjx-container[jax=&quot;SVG&quot;] {
  direction: ltr;
}

mjx-container[jax=&quot;SVG&quot;] &gt; svg {
  overflow: visible;
  min-height: 1px;
  min-width: 1px;
}

mjx-container[jax=&quot;SVG&quot;] &gt; svg a {
  fill: blue;
  stroke: blue;
}

mjx-assistive-mml {
  position: absolute !important;
  top: 0px;
  left: 0px;
  clip: rect(1px, 1px, 1px, 1px);
  padding: 1px 0px 0px 0px !important;
  border: 0px !important;
  display: block !important;
  width: auto !important;
  overflow: hidden !important;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

mjx-assistive-mml[display=&quot;block&quot;] {
  width: 100% !important;
}

mjx-container[jax=&quot;SVG&quot;][display=&quot;true&quot;] {
  display: block;
  text-align: center;
  margin: 1em 0;
}

mjx-container[jax=&quot;SVG&quot;][display=&quot;true&quot;][width=&quot;full&quot;] {
  display: flex;
}

mjx-container[jax=&quot;SVG&quot;][justify=&quot;left&quot;] {
  text-align: left;
}

mjx-container[jax=&quot;SVG&quot;][justify=&quot;right&quot;] {
  text-align: right;
}

g[data-mml-node=&quot;merror&quot;] &gt; g {
  fill: red;
  stroke: red;
}

g[data-mml-node=&quot;merror&quot;] &gt; rect[data-background] {
  fill: yellow;
  stroke: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; line[data-line], svg[data-table] &gt; g &gt; line[data-line] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; rect[data-frame], svg[data-table] &gt; g &gt; rect[data-frame] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; .mjx-dashed, svg[data-table] &gt; g &gt; .mjx-dashed {
  stroke-dasharray: 140;
}

g[data-mml-node=&quot;mtable&quot;] &gt; .mjx-dotted, svg[data-table] &gt; g &gt; .mjx-dotted {
  stroke-linecap: round;
  stroke-dasharray: 0,140;
}

g[data-mml-node=&quot;mtable&quot;] &gt; g &gt; svg {
  overflow: visible;
}

[jax=&quot;SVG&quot;] mjx-tool {
  display: inline-block;
  position: relative;
  width: 0;
  height: 0;
}

[jax=&quot;SVG&quot;] mjx-tool &gt; mjx-tip {
  position: absolute;
  top: 0;
  left: 0;
}

mjx-tool &gt; mjx-tip {
  display: inline-block;
  padding: .2em;
  border: 1px solid #888;
  font-size: 70%;
  background-color: #F8F8F8;
  color: black;
  box-shadow: 2px 2px 5px #AAAAAA;
}

g[data-mml-node=&quot;maction&quot;][data-toggle] {
  cursor: pointer;
}

mjx-status {
  display: block;
  position: fixed;
  left: 1em;
  bottom: 1em;
  min-width: 25%;
  padding: .2em .4em;
  border: 1px solid #888;
  font-size: 90%;
  background-color: #F8F8F8;
  color: black;
}

foreignObject[data-mjx-xml] {
  font-family: initial;
  line-height: normal;
  overflow: visible;
}

mjx-container[jax=&quot;SVG&quot;] path[data-c], mjx-container[jax=&quot;SVG&quot;] use[data-c] {
  stroke-width: 3;
}
&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;Over the past few months, I rewrote most of my test suite. It was a cleanup effort as well as a chance to rethink how I approach testing, something I’ve been neglecting for most of my programming career.&lt;/p&gt;
&lt;aside id=&quot;sn1&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;1.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Only the tests were slop, I actually put a lot of thought into the code itself! &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;There were two main reasons for rewriting my tests. The first reason was that the codebase I’ve been working on has grown, and more people are now working on it, but the existing tests weren’t adequate. At the time, they were the largest source of tech debt in the project, and I’m mostly to blame for that… Most of the tests were half-broken, vibe-coded slop just there to satisfy &lt;a href=&quot;https://about.codecov.io/&quot;&gt;Codecov&lt;/a&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn1&quot; id=&quot;snref1&quot;&gt;[1]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The other reason was that I wanted to try generative testing. I read and consume a lot of content about generative testing, e.g. from &lt;a href=&quot;https://tigerbeetle.com/&quot;&gt;TigerBeetle&lt;/a&gt; and &lt;a href=&quot;https://antithesis.com/&quot;&gt;Antithesis&lt;/a&gt;, and I’ve always wanted to apply it to my own projects.&lt;/p&gt;
&lt;p&gt;These eventually pushed me to delete my existing tests and rewrite them the way I imagined they should be. This post goes through that process and what I learned from it.&lt;/p&gt;
&lt;aside class=&quot;toc&quot;&gt;&lt;div&gt;
      &lt;h2 id=&quot;toc&quot;&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#toc&quot; class=&quot;header-anchor&quot;&gt;Table of Contents&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#notes-on-terminology&quot;&gt;Notes on Terminology&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#rethinking-my-testing-approach&quot;&gt;Rethinking My Testing Approach&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#properties-over-examples&quot;&gt;Properties Over Examples&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#system-wide-fuzzing&quot;&gt;System-Wide Fuzzing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#testing-with-intent&quot;&gt;Testing with Intent&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#the-system-under-test&quot;&gt;The System Under Test&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#property-based-testing-techniques&quot;&gt;Property-Based Testing Techniques&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#testing-data-structures&quot;&gt;Testing Data Structures&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#swarm-testing&quot;&gt;Swarm Testing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#concerns-with-model-based-testing&quot;&gt;Concerns with Model-Based Testing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#reducing-the-state-space&quot;&gt;Reducing the State Space&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#on-tooling&quot;&gt;On Tooling&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#(almost)-deterministic-simulation-testing&quot;&gt;(Almost) Deterministic Simulation Testing&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#how-it-works&quot;&gt;How it Works&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#expectations-vs-reality&quot;&gt;Expectations VS Reality&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#end-to-end-fuzzing&quot;&gt;End-to-End Fuzzing&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;notes-on-terminology&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#notes-on-terminology&quot;&gt;Notes on Terminology&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before going into more details, I just want to clarify some terms I’ll use. There are a lot of overloaded terms in testing (and software in general), and people often use the same words to mean different things. You may disagree with these, that’s fine. This is just what I mean when I use them.&lt;/p&gt;
&lt;p&gt;I use only two scopes for tests:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Unit tests&lt;/strong&gt;: Tests scoped to any component or subsystem of the full system. The size doesn’t matter. It can be a single function, a module, or a group of modules. Some people will call tests that span multiple units “integration tests”, but I disagree. That definition is too ambiguous. I use the term “integration” to describe a different concept.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System tests&lt;/strong&gt;: Tests scoped to the entire system. I also consider tests that replace production components with test versions to still be system tests, as long as the overall system is exercised as a whole.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I use the term &lt;strong&gt;integration tests&lt;/strong&gt; for tests (any scope) that interact with “black boxes”, e.g. third-party services, filesystems, networks, databases, etc. Or, in functional programming terms, impure tests. Tests without “integration” in their name implicitly mean pure tests. Also, I use the term &lt;strong&gt;end-to-end (E2E) tests&lt;/strong&gt; specifically for system-scoped integration tests.&lt;/p&gt;
&lt;h2 id=&quot;rethinking-my-testing-approach&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#rethinking-my-testing-approach&quot;&gt;Rethinking My Testing Approach&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before starting the rewrite, I spent some time thinking about how to approach it and what my tests should look like. As you can already tell from the title of this post, most of the rewrite involves writing generative tests.&lt;/p&gt;
&lt;p&gt;Generative testing is basically a catch-all term for tests where you write code that generates test cases, instead of you specifying the test cases themselves. This includes randomised approaches like &lt;a href=&quot;https://antithesis.com/resources/property_based_testing/&quot;&gt;property-based testing (PBT)&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/Fuzzing&quot;&gt;fuzzing&lt;/a&gt;, and non-random ones where you try every possible input.&lt;/p&gt;
&lt;aside id=&quot;sn2&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;2.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I like comparing PBT and fuzzing to &lt;a href=&quot;https://en.wikipedia.org/wiki/Convergent_evolution&quot;&gt;convergent evolution&lt;/a&gt;, which is when species of different lineages evolve similar features. PBT came from functional programming, fuzzing came much earlier from systems/security. The approach is similar, but their focus is different (program correctness vs security issues). Yes, I got this from &lt;a href=&quot;https://antithesis.com/product/how_is_antithesis_different/#property-based-testing-vs.-antithesis&quot;&gt;Antithesis&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Note, I’ll use the terms “PBT” and “fuzzing” interchangeably going forward. Both involve using randomised input to test some property of the system. There are subtle differences, but in practice they’re minor enough that I just consider them the same. &lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn2&quot; id=&quot;snref2&quot;&gt;[2]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here are some of the main ideas that guided the rewrite:&lt;/p&gt;
&lt;h3 id=&quot;properties-over-examples&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#properties-over-examples&quot;&gt;Properties Over Examples&lt;/a&gt;&lt;/h3&gt;
&lt;aside id=&quot;sn3&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;3.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I recommend reading &lt;a href=&quot;https://antithesis.com/resources/property_based_testing/&quot;&gt;Antithesis&#39;s PBT primer&lt;/a&gt; first, and then &lt;a href=&quot;https://fsharpforfunandprofit.com/series/property-based-testing/&quot;&gt;F# for Fun and Profit&#39;s PBT series&lt;/a&gt; for some more practical tips. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Property-based testing is a testing approach where you specify properties of your code and throw randomised input at it to break those properties. I’m not going to explain it in detail here as there are already plenty of good resources online, and I don’t have anything new to add.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn3&quot; id=&quot;snref3&quot;&gt;[3]&lt;/a&gt;
        &lt;/sup&gt; Here, I’ll just talk about my case for PBT.&lt;/p&gt;
&lt;p&gt;While the system is still small, I wanted to catch most bugs early so they don’t compound as the system grows. The existing example-based tests are limited in this regard, as they only cover cases I already know. PBT is much more effective as they uncover cases and scenarios that we forgot or may never have thought of.&lt;/p&gt;
&lt;p&gt;In other words, &lt;strong&gt;property-based tests find bugs; example-based tests catch regressions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;My plan was to replace most of the existing tests with property-based tests, keeping a few select example-based tests, which are still useful for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Smoke tests&lt;/strong&gt;: Sanity checks of the happy path, sometimes including common error cases . I mainly use them for code that doesn’t need extensive testing, e.g. thin wrappers or simple glue code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation&lt;/strong&gt;: Example-based tests are more readable, so I leave some to illustrate API usage or internal behaviour.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regression tests&lt;/strong&gt;: Dedicated tests to ensure new changes don’t reintroduce known bugs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;system-wide-fuzzing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#system-wide-fuzzing&quot;&gt;System-Wide Fuzzing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;System tests are usually the most valuable as they validate your system as a whole. The hardest bugs usually emerge only when components are used together. PBT/fuzzing is scope-agnostic, so it makes a whole lot of sense to also apply it in system tests.&lt;/p&gt;
&lt;aside id=&quot;sn4&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;4.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;My real reason for adopting DST is just because I think it&#39;s cool. It&#39;s what got me interested in TigerBeetle in the first place. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;There are two approaches I went with for this: the infamous &lt;a href=&quot;https://www.youtube.com/watch?v=4fFDFbi3toc&quot;&gt;deterministic simulation testing (DST)&lt;/a&gt; and traditional full-system fuzzing.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn4&quot; id=&quot;snref4&quot;&gt;[4]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;DST is basically just a system-scoped property-based test. Sure, it involves extra steps like replacing non-deterministic components (e.g. network, disk, clock, etc.) with deterministic counterparts, but the overall flow is the same as regular fuzzing or stateful PBT. If you want to learn more about DST, I recommend reading &lt;a href=&quot;https://antithesis.com/resources/deterministic_simulation_testing/&quot;&gt;Antithesis’s DST primer&lt;/a&gt; or &lt;a href=&quot;https://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html&quot;&gt;Phil Eaton’s blog post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The main pitch of DST is that it finds the most outlandish bugs for you while keeping them perfectly reproducible, but as I eventually learned, it’s not the silver bullet I initially thought it was. More on this later!&lt;/p&gt;
&lt;p&gt;The other approach is an E2E test where we fuzz the system in its production configuration, i.e. without replacing non-deterministic components. This means that the test runs in wall clock time and that they may be flaky, but it provides more coverage as the bugs it catches may not show up in DST. This follows the same principles as &lt;a href=&quot;https://tigerbeetle.com/blog/2025-02-13-a-descent-into-the-vortex/&quot;&gt;TigerBeetle’s Vortex&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;testing-with-intent&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#testing-with-intent&quot;&gt;Testing with Intent&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This isn’t related to generative testing, but I still think it’s worth including. One thing I started to pay more attention to now in my tests is the intent or thought behind it.&lt;/p&gt;
&lt;p&gt;It’s a similar concept to design. If you read design case studies or write-ups, you might be surprised at the amount of thought put into seemingly trivial details. Every decision is made on purpose. It’s the same for code. I think through the program’s architecture and how to structure the code, but I’ve always neglected doing it for tests.&lt;/p&gt;
&lt;p&gt;You may argue that tests already have a purpose by default, e.g. to verify correctness, catch regressions, etc. I’m talking about stuff beyond that. Stuff like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Is this test testing intended behaviour, or is it just copying your implementation? The latter gives you false confidence, e.g. it can cover every branch and still be wrong.&lt;/li&gt;
&lt;li&gt;How should I test this? Not everything needs the same level of testing. Sometimes a simple smoke test is enough vs a full-blown exhaustive test.&lt;/li&gt;
&lt;li&gt;Do I need a test for this? Do I care enough about this to add a test? Every code you add is code you have to maintain.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thinking about a test’s intent also forces you to reason about the &lt;a href=&quot;https://en.wikipedia.org/wiki/System_under_test&quot;&gt;system under test (SUT)&lt;/a&gt;. I treat it as a design exercise; some people describe it like an &lt;a href=&quot;https://spawn-queue.acm.org/doi/10.1145/3291276.3294770&quot;&gt;experiment’s hypothesis&lt;/a&gt;. I’ve also started adding comments on my tests to make them easier for other developers to understand, e.g. why it exists, what behaviour/properties it checks, how it actually verifies them, etc.&lt;/p&gt;
&lt;h2 id=&quot;the-system-under-test&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#the-system-under-test&quot;&gt;The System Under Test&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here’s a brief overview of the SUT for context in the examples later. All examples in this post are drawn from &lt;strong&gt;Cardinal&lt;/strong&gt;, the game server component of the &lt;a href=&quot;https://github.com/Argus-Labs/world-engine&quot;&gt;World Engine&lt;/a&gt; stack. It’s written in &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To be more specific, Cardinal is a &lt;a href=&quot;https://gamedev.stackexchange.com/questions/81608/what-is-a-tick-in-the-context-of-game-development&quot;&gt;tick-based&lt;/a&gt; game server framework built around an &lt;a href=&quot;https://docs.unity3d.com/Packages/com.unity.entities@6.5/manual/concepts-ecs.html&quot;&gt;archetypal&lt;/a&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Entity_component_system&quot;&gt;entity-component-system (ECS)&lt;/a&gt; core. Users implement game logic as systems, which are executed each tick to advance the world state. Cardinal communicates with other services through &lt;a href=&quot;https://nats.io/&quot;&gt;NATS&lt;/a&gt; and persists state using periodic snapshots.&lt;/p&gt;
&lt;p&gt;Every tick, Cardinal injects commands received from external services (e.g. game clients) into the ECS core and runs the registered systems. These systems process commands, update the world state, and emit events. After the tick ends, event handlers publish the emitted events to NATS for other services to consume.&lt;/p&gt;
&lt;p&gt;Here’s a diagram showing Cardinal’s high-level architecture:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/generative-testing/krFIKhquxE-1420.webp&quot; alt=&quot;Cardinal&#39;s architecture diagram&quot; width=&quot;1420&quot; height=&quot;1444&quot;&gt;&lt;/p&gt;
&lt;p&gt;Ok, hope that made sense. Now, on to how I wrote the tests.&lt;/p&gt;
&lt;h2 id=&quot;property-based-testing-techniques&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#property-based-testing-techniques&quot;&gt;Property-Based Testing Techniques&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started by writing unit tests first, on small units, just to familiarise myself with PBT before moving on to larger units and the full system. This section lists most of the techniques I learned while doing it.&lt;/p&gt;
&lt;p&gt;The hardest part of PBT is figuring out what properties to test, so I think one of the best ways to learn is to just see a lot of examples to start recognising patterns. Most of the things mentioned here came from &lt;a href=&quot;https://tigerbeetle.com/blog/&quot;&gt;TigerBeetle’s blog&lt;/a&gt; and &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle&quot;&gt;source code&lt;/a&gt;, as that’s where I learned them. If you’re also interested in PBT/fuzzing, definitely give them a read!&lt;/p&gt;
&lt;h3 id=&quot;testing-data-structures&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#testing-data-structures&quot;&gt;Testing Data Structures&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Cardinal is mostly made up of data structures. Testing them is a bit different from testing pure functions often used in PBT examples. With pure functions, you simply assert on the output. With data structures, there are two ways to do it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Assert internal properties (invariants).&lt;/strong&gt;
An invariant is a condition that must always be true. You can test these if your tests have access to internal fields, or by placing &lt;a href=&quot;https://tigerstyle.dev/#assertions&quot;&gt;assertions&lt;/a&gt; directly in the implementation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Assert against a model.&lt;/strong&gt;
This is called &lt;strong&gt;model-based testing (MBT)&lt;/strong&gt;, or stateful PBT, state machine testing, differential testing, etc. In MBT, you compare the behaviour of your implementation against a model with the &lt;strong&gt;same semantics&lt;/strong&gt;. The model can be a simplified implementation or a trusted reference, e.g. a data structure from your language’s standard library.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The idea is simple: run the same operations on both your implementation and the model, and assert that their states match.&lt;/p&gt;
&lt;p&gt;I’ll use Cardinal’s &lt;code&gt;componentManager&lt;/code&gt; to illustrate. Here’s what it looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/component.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;componentManager&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	nextID    componentID            &lt;span class=&quot;token comment&quot;&gt;// The next available component ID&lt;/span&gt;
	catalog   &lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;componentID &lt;span class=&quot;token comment&quot;&gt;// Component name -&amp;gt; component ID&lt;/span&gt;
	factories &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;columnFactory        &lt;span class=&quot;token comment&quot;&gt;// Component ID -&amp;gt; column factory&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// register registers a new component type and returns its ID.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cm &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;componentManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; factory columnFactory&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;componentID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// getID returns a component&#39;s ID given its name.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cm &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;componentManager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;getID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;componentID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;While the manager does more internally, the bulk of its work is component registrations, which basically involves mapping component IDs to their names. Since this behaviour is essentially like a map, I used Go’s built-in &lt;code&gt;map&lt;/code&gt; as the model. Here’s a simplified version of the test:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/component_internal_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TestComponent_RegisterModelFuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	prng &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	impl &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newComponentManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	model &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;componentID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	operations &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;opRegister&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opGetID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Just string constants&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10_000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RandOp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Pick a random operation&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; opRegister&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			name &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randValidComponentName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			implID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implErr &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; impl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;register&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			modelID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelExists &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

			require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implErr&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; modelExists &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;token comment&quot;&gt;// Property: duplicate registrations return same ID.&lt;/span&gt;
				assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				model&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; implID
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; opGetID&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			name &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randProbablyRegisteredComponentName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Can return non-existent keys&lt;/span&gt;
			implID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implErr &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; impl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			modelID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelExists &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; model&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

			&lt;span class=&quot;token comment&quot;&gt;// Property: getID returns same existence and value as model.&lt;/span&gt;
			assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelExists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implErr &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; modelExists &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Property: implementation matches model.&lt;/span&gt;
	assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; impl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;catalog&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;model&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelID &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; model &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		implID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; impl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Equal&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; modelID&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; implID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now you might be thinking: wait, doesn’t the implementation also use a &lt;code&gt;map&lt;/code&gt;? The model is the same as the implementation. Your test essentially just compares two maps. Is it even doing anything?&lt;/p&gt;
&lt;p&gt;I had similar doubts too when I first wrote this. The benefits of this test aren’t obvious at first (to me at least), but it becomes clear once you change the implementation. For example, I could replace the map with an exotic data structure, e.g. to get a 100x performance boost. This test will still work (as long as the semantics are the same, of course) because it tests the behaviour, not the implementation.&lt;/p&gt;
&lt;p&gt;Note, MBT and internal assertions aren’t mutually exclusive. Ideally, you should use both. It’s not shown in the example above, but I also have assertions in the implementation to guard against invalid states. They check for different things: &lt;strong&gt;internal assertions ensure invariants hold, MBT ensures it behaves correctly&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;swarm-testing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#swarm-testing&quot;&gt;Swarm Testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I complement all my model-based tests with &lt;a href=&quot;https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/&quot;&gt;swarm testing&lt;/a&gt;. In the previous example, the test picks a random operation, where every operation has the same chance of being selected (a uniform distribution). In practice, this usually doesn’t lead to interesting scenarios.&lt;/p&gt;
&lt;p&gt;The idea behind swarm testing is to randomise the test configuration. In our case, that means randomising which operations are performed. We can do this by picking a subset of all possible operations and assigning each a random weight (probability of being picked).&lt;/p&gt;
&lt;p&gt;TigerBeetle uses &lt;a href=&quot;https://www.openmymind.net/Basic-MetaProgramming-in-Zig/&quot;&gt;Zig’s metaprogramming magic&lt;/a&gt; to automatically derive operations from a struct’s fields. You could probably do this in Go using &lt;a href=&quot;https://go.dev/blog/laws-of-reflection&quot;&gt;runtime reflection&lt;/a&gt;, which I only realised while writing this… What I ended up doing was writing helper functions that take a hardcoded list of operations and randomises them:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/testutils/random.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;OpWeights&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;uint64&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Operation names -&amp;gt; weights&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// RandOpWeights selects a random subset of operations and assigns random weights to them.&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// At minimum 1 operation is enabled, at most all operations are enabled.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;RandOpWeights&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rand&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ops &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; OpWeights &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;That&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;you need multiple operations to randomize&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Randomly select how many operations to enable (1 to len(ops)).&lt;/span&gt;
	numEnabled &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IntN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Shuffle and take the first numEnabled operations.&lt;/span&gt;
	shuffled &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; slices&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ops&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Shuffle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;shuffled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; j &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		shuffled&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shuffled&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;j&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; shuffled&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;j&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; shuffled&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	weights &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; numEnabled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; numEnabled &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		weights&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;shuffled&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;IntN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; weights
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// RandWeightedOp returns a random weighted operation from a OpWeights.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;RandWeightedOp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;r &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rand&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; ops OpWeights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; total &lt;span class=&quot;token builtin&quot;&gt;uint64&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; ops &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		total &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; weight
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	pick &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; r&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Uint64N&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;total&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; op&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weight &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; ops &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; pick &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; weight &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; op
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		pick &lt;span class=&quot;token operator&quot;&gt;-=&lt;/span&gt; weight
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token function&quot;&gt;panic&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;unreachable&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Here’s what the previous test looks like with swarm testing:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/component_internal_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TestComponent_RegisterModelFuzz&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	prng &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Get a random weighted subset of your operations.&lt;/span&gt;
	operations &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;opRegister&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opGetID&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	weights &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RandOpWeights&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; operations&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;10_000&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		op &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RandWeightedOp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; weights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; op &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; opRegister&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; opGetID&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Initially, I thought regular MBT was enough, until I found a bug in my code. It’s the type of bug that can be found faster through swarm testing. Here’s a snippet of the buggy code from Cardinal’s event manager type. Can you spot the issue?&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/event/event.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;NewManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelCapacity &lt;span class=&quot;token builtin&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; Manager &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Manager&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		channel&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;chan&lt;/span&gt; Event&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; channelCapacity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Enqueue enqueues an event into the Manager.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;m &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;Manager&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;Enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event Event&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;channel &lt;span class=&quot;token operator&quot;&gt;&amp;lt;-&lt;/span&gt; event
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The bug here is that &lt;code&gt;Enqueue&lt;/code&gt; &lt;strong&gt;blocks when the channel capacity is reached&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The existing tests never found it, and it never happened during local development, so this bug went unnoticed for a long time. While I was writing the model tests, I experimented with increasing the number of operations. The tests eventually got stuck, revealing the bug.&lt;/p&gt;
&lt;p&gt;This is exactly the kind of bug swarm testing helps catch. The original test runs both enqueue and dequeue operations, so the channel never fills up. If the dequeue operation is disabled or is less likely to be picked, the channel quickly reaches capacity, and the bug becomes obvious!&lt;/p&gt;
&lt;p&gt;After fixing the bug, I added a regression test to ensure it doesn’t happen again:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/event/event_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TestEvent_EnqueueChannelFull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	synctest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; channelCapacity &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; totalEvents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; channelCapacity &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Well beyond channel capacity&lt;/span&gt;

		impl &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewManager&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;channelCapacity&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Enqueue more events than channel capacity.&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; i &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; totalEvents &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				impl&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Event&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;Kind&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;KindDefault&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Payload&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; i&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Wait for all goroutines to complete or durably block.&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// If enqueue blocks, synctest.Test will detect deadlock and fail.&lt;/span&gt;
		synctest&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Wait&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Extra property checks...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This test uses Go’s new &lt;a href=&quot;https://pkg.go.dev/testing/synctest&quot;&gt;&lt;code&gt;testing/synctest&lt;/code&gt;&lt;/a&gt; to detect deadlocks. Unlike traditional timeout-based tests, goroutines spawned inside the &lt;code&gt;synctest.Test&lt;/code&gt; bubble are tracked, which allows the runtime to detect deadlocks/leaks quicker and deterministically.&lt;/p&gt;
&lt;h3 id=&quot;concerns-with-model-based-testing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#concerns-with-model-based-testing&quot;&gt;Concerns with Model-Based Testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A big concern I had when writing these tests was how do I know whether my model is correct. I’m usually confident when the models are from Go’s standard library, since they’ve probably been tested to death by maintainers and by users in production.&lt;/p&gt;
&lt;p&gt;But sometimes the standard library doesn’t have anything with the same behaviour as the data structure I’m testing. In these cases, I’m forced to write my own simplified model implementation. The hard part was verifying the model. How do I know if it’s correct? Do I test my model? What do I test it against? Should I test my tests? It’s turtles all the way down.&lt;/p&gt;
&lt;aside id=&quot;sn5&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;5.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;There are some approaches that address this problem (somewhat), e.g. &lt;a href=&quot;https://en.wikipedia.org/wiki/metamorphic_testing&quot;&gt;metamorphic testing&lt;/a&gt;. It solves this by using oracles derived from the tests themselves (through special properties called metamorphic relations), but it&#39;s not a universal solution. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Turns out this is a well-known problem called the &lt;a href=&quot;https://en.wikipedia.org/wiki/Test_oracle&quot;&gt;test oracle problem&lt;/a&gt;. A test oracle is basically what determines the correct output of a test. In example-based tests, you are the oracle because you specify the expected output. In MBT, the model is the oracle. As far as I know, there’s no complete solution to this problem.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn5&quot; id=&quot;snref5&quot;&gt;[5]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;My only practical suggestion is to make your models (and tests in general) “obviously correct”. Prioritise simplicity and clarity above everything else. Keep the code short and easy to audit. It doesn’t matter if it’s slow or inelegant. Ideally, you should be able to convince yourself (and others) that the code is correct just by reading it.&lt;/p&gt;
&lt;h3 id=&quot;reducing-the-state-space&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#reducing-the-state-space&quot;&gt;Reducing the State Space&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some behaviours are surprisingly difficult to test, especially when they depend on many interacting variables. The number of possible variable combinations grows quickly, leading to a state space explosion. When this happens, uniform random sampling becomes less effective as most samples fall into “empty space” rather than edges and boundaries where bugs often hide.&lt;/p&gt;
&lt;p&gt;Here’s an example. Cardinal’s ECS has the concept of &lt;strong&gt;archetypes&lt;/strong&gt;. An archetype represents a unique combination of component types. It stores a list of entities belonging to that archetype as well as their component data. Here’s what it looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/archetype.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;archetype&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	id         archetypeID      &lt;span class=&quot;token comment&quot;&gt;// Corresponds to the index in the archetypes array&lt;/span&gt;
	components bitmap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Bitmap    &lt;span class=&quot;token comment&quot;&gt;// Bitmap of components contained in this archetype&lt;/span&gt;
	rows       sparseSet        &lt;span class=&quot;token comment&quot;&gt;// Maps entity ID -&amp;gt; row index in entities and columns&lt;/span&gt;
	entities   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;EntityID       &lt;span class=&quot;token comment&quot;&gt;// List of entities of this archetype&lt;/span&gt;
	columns    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;abstractColumn &lt;span class=&quot;token comment&quot;&gt;// List of columns containing component data&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;When components are added to and/or removed from an entity, it is moved to a different archetype with the required component set. This operation involves creating the entity in the destination archetype, copying its components, and removing it from the source archetype:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/archetype.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// moveEntity moves an entity from one archetype to another.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;archetype&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;moveEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;destination &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;archetype&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eid EntityID&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; a &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; destination &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	row&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exists &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;That&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;entity is not in archetype&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Create a new entity in the destination archetype with the id.&lt;/span&gt;
	destination&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	newRow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exists &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; destination&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rows&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;That&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;exists&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;new entity isn&#39;t created in the destination archetype&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Move entity&#39;s components to the new archetype.&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; dst &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; destination&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;columns &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; src &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;columns &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; dst&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				value &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getAbstract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
				dst&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAbstract&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;newRow&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// Remove the entity from the current archetype, which also updates the row mapping.&lt;/span&gt;
	a&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;removeEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There are a lot of variables involved in a move operation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The source and destination archetypes.&lt;/li&gt;
&lt;li&gt;The number of components added, removed, or copied.&lt;/li&gt;
&lt;li&gt;The number of entities in both archetypes.&lt;/li&gt;
&lt;li&gt;Which entity is being moved.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If we only consider the source and destination archetypes, the input space is already huge. If there are &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: 0;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;2.009ex&quot; height=&quot;1.545ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -683 888 683&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;mi&quot;&gt;&lt;use data-c=&quot;1D441&quot; xlink:href=&quot;#MJX-TEX-I-1D441&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; component types, there are &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: 0;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;2.74ex&quot; height=&quot;1.914ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -846 1210.9 846&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(533,363) scale(0.707)&quot;&gt;&lt;use data-c=&quot;1D441&quot; xlink:href=&quot;#MJX-TEX-I-1D441&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; possible archetypes (&lt;a href=&quot;https://www.geeksforgeeks.org/dsa/number-distinct-subsets-set/&quot;&gt;distinct subsets&lt;/a&gt;). Cardinal supports up to &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: 0;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;2.919ex&quot; height=&quot;1.887ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -833.9 1290.1 833.9&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;mn&gt;32&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; component types, so the theoretical number of archetypes is &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: 0;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;3.383ex&quot; height=&quot;2.155ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -952.7 1495.2 952.7&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;mn&gt;32&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;. This means there are &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: 0;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;8.526ex&quot; height=&quot;2.155ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -952.7 3768.5 952.7&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(1495.2,0)&quot;&gt;&lt;use data-c=&quot;D7&quot; xlink:href=&quot;#MJX-TEX-I-D7&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;msup&quot; transform=&quot;translate(2273.2,0)&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;TeXAtom&quot; transform=&quot;translate(533,363) scale(0.707)&quot; data-mjx-texclass=&quot;ORD&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;mn&gt;32&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;mi&gt;×&lt;/mi&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;msup&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mrow data-mjx-texclass=&quot;ORD&quot;&gt;&lt;mn&gt;32&lt;/mn&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/mrow&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; source-destination pairs. That’s a number with &lt;strong&gt;258,583 digits&lt;/strong&gt;!&lt;/p&gt;
&lt;aside id=&quot;sn6&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;6.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;This is probably why shrinking in pbt works. While the original intent is to create a simpler representation for debugging, the fact remains that the same bugs found in larger inputs can usually be reproduced in smaller ones. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;To make testing this more effective, I intentionally limited the size of the state space. The rationale is based on the &lt;a href=&quot;https://dspace.mit.edu/bitstream/handle/1721.1/149864/MIT-LCS-TR-735.pdf&quot;&gt;small scope hypothesis&lt;/a&gt;, which argues that most bugs can be found within small input sizes.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn6&quot; id=&quot;snref6&quot;&gt;[6]&lt;/a&gt;
        &lt;/sup&gt; Larger inputs don’t necessarily lead to new execution paths. Borrowing the words of &lt;a href=&quot;https://matklad.github.io/&quot;&gt;Matklad&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Most of the bugs involve small, tricky examples. If a sorting routine breaks on some array with ten thousand elements it’s highly likely that there’s a much smaller array (a handful of elements), which exposes the same bug.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With a smaller scope, we can feasibly test all possible variable combinations exhaustively. Some of you might already know where I’m heading here. Yes, I’m talking about &lt;a href=&quot;https://matklad.github.io/2021/11/07/generate-all-the-things.html&quot;&gt;Exhaustigen&lt;/a&gt;. It’s a helper library for generating combinations of multiple variables exhaustively. I ported TigerBeetle’s Zig version to Go, which you can find &lt;a href=&quot;https://github.com/Argus-Labs/world-engine/blob/ddefa5b7cc9992e5db41a5d181520d7e2f87f0db/pkg/testutils/exhaustigen.go&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For my archetype move test, I reduced my variables to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maximum 6 component types, which results in 64 possible archetypes. This produces &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: -0.186ex;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;10.792ex&quot; height=&quot;2.165ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -875 4770.1 957&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;34&quot; xlink:href=&quot;#MJX-TEX-N-34&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(1033,404.1) scale(0.707)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mo&quot; transform=&quot;translate(1714.3,0)&quot;&gt;&lt;use data-c=&quot;3D&quot; xlink:href=&quot;#MJX-TEX-N-3D&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(2770.1,0)&quot;&gt;&lt;use data-c=&quot;34&quot; xlink:href=&quot;#MJX-TEX-N-34&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;30&quot; xlink:href=&quot;#MJX-TEX-N-30&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;39&quot; xlink:href=&quot;#MJX-TEX-N-39&quot; transform=&quot;translate(1000,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot; transform=&quot;translate(1500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;64&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;4096&lt;/mn&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; source-destination pairs. I arrived at this number after some experimentation. Higher counts lead to exponentially longer test runs.&lt;/li&gt;
&lt;li&gt;1-3 entities in the source and 0-2 entities in the destination. The source must have at least one entity to move.&lt;/li&gt;
&lt;li&gt;The entity to be moved is randomly chosen from the source. Entities and components are stored in slices under the hood. This verifies moves work correctly regardless of whether the entity is at the beginning, middle, or end of the slice.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;With that, my number of test cases becomes: &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: -0.439ex;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;24.01ex&quot; height=&quot;1.971ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -677 10612.2 871&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;34&quot; xlink:href=&quot;#MJX-TEX-N-34&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;30&quot; xlink:href=&quot;#MJX-TEX-N-30&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;39&quot; xlink:href=&quot;#MJX-TEX-N-39&quot; transform=&quot;translate(1000,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot; transform=&quot;translate(1500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(2000,0)&quot;&gt;&lt;use data-c=&quot;D7&quot; xlink:href=&quot;#MJX-TEX-I-D7&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(2778,0)&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(3278,0)&quot;&gt;&lt;use data-c=&quot;D7&quot; xlink:href=&quot;#MJX-TEX-I-D7&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(4056,0)&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(4556,0)&quot;&gt;&lt;use data-c=&quot;D7&quot; xlink:href=&quot;#MJX-TEX-I-D7&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(5334,0)&quot;&gt;&lt;use data-c=&quot;33&quot; xlink:href=&quot;#MJX-TEX-N-33&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mo&quot; transform=&quot;translate(6111.8,0)&quot;&gt;&lt;use data-c=&quot;3D&quot; xlink:href=&quot;#MJX-TEX-N-3D&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(7167.6,0)&quot;&gt;&lt;use data-c=&quot;31&quot; xlink:href=&quot;#MJX-TEX-N-31&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;31&quot; xlink:href=&quot;#MJX-TEX-N-31&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;30&quot; xlink:href=&quot;#MJX-TEX-N-30&quot; transform=&quot;translate(1000,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mo&quot; transform=&quot;translate(8667.6,0)&quot;&gt;&lt;use data-c=&quot;2C&quot; xlink:href=&quot;#MJX-TEX-N-2C&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(9112.2,0)&quot;&gt;&lt;use data-c=&quot;35&quot; xlink:href=&quot;#MJX-TEX-N-35&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;39&quot; xlink:href=&quot;#MJX-TEX-N-39&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot; transform=&quot;translate(1000,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;mn&gt;4096&lt;/mn&gt;&lt;mi&gt;×&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;mi&gt;×&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;mi&gt;×&lt;/mi&gt;&lt;mn&gt;3&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;110&lt;/mn&gt;&lt;mo&gt;,&lt;/mo&gt;&lt;mn&gt;592&lt;/mn&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;. This is small enough to run in under a second, but large enough to go beyond the simplest cases.&lt;/p&gt;
&lt;p&gt;Here’s a simplified version of the test:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/internal/ecs/archetype_internal_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TestArchetype_MoveExhaustive&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	prng &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Generate all possible subsets (archetype) with N component types.&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; maxComponents &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;
	pool &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newArchetypePool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;maxComponents&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Exhaustively test combinations of source * destination * entity count * moved entity row.&lt;/span&gt;
	gen &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewGen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Done&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		src &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pool&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Randomize source&lt;/span&gt;
		dst &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; pool&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pool&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Randomize destination&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Populate source with 1-3 entities, destination with 0-2 entities.&lt;/span&gt;
		countSrc &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Randomize source entity count&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; eid &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; countSrc &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;EntityID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
		countDst &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Range&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Randomize destination entity count&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; eid &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;range&lt;/span&gt; countDst &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			dst&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;newEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;EntityID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;countSrc &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Entity IDs are globally unique&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Pick which entity to move.&lt;/span&gt;
		row &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; gen&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Index&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;len&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;entities&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Randomize moved entity row&lt;/span&gt;
		eid &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;entities&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;row&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; src &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; dst &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NotPanics&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dst&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;self move should be a no-op and must not panic&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

		src&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;moveEntity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;dst&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; eid&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Do the actual move.&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Assert properties here...&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Exhaustigen’s API is so nicely designed. It still amazes me that this small amount of code is enough to cover all variable combinations. It does some intricate machinery under the hood, so if you’re curious about how it works, check out the &lt;a href=&quot;https://matklad.github.io/2021/11/07/generate-all-the-things.html&quot;&gt;original blog post&lt;/a&gt;!&lt;/p&gt;
&lt;h3 id=&quot;on-tooling&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#on-tooling&quot;&gt;On Tooling&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I didn’t use any third-party PBT libraries, e.g. &lt;a href=&quot;https://github.com/flyingmutant/rapid&quot;&gt;Rapid&lt;/a&gt;, or Go’s &lt;code&gt;testing/quick&lt;/code&gt; and &lt;code&gt;testing.F&lt;/code&gt;. For my use cases, Go’s &lt;code&gt;rand.Rand&lt;/code&gt; &lt;a href=&quot;https://en.wikipedia.org/wiki/Pseudorandom_number_generator&quot;&gt;PRNG&lt;/a&gt; already provided most of the functionality I needed.&lt;/p&gt;
&lt;p&gt;As I wrote the tests, I gradually extracted utilities once the patterns became clear. These helpers live in a small &lt;code&gt;testutils&lt;/code&gt; package. The first thing I added was support for a global seed for the test PRNGs.&lt;/p&gt;
&lt;p&gt;Since PBT relies on randomness, rerunning the same test can produce different results. To make failures reproducible, I seed all test PRNGs with the same value. If a test fails, I can reproduce it by rerunning it using the same seed:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;TEST_SEED&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;0x0123456789abcdef go &lt;span class=&quot;token builtin class-name&quot;&gt;test&lt;/span&gt; ./&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The seed is printed if you run the tests with the &lt;code&gt;-v&lt;/code&gt; (verbose) flag. Here’s the code for it:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/testutils/random.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; Seed &lt;span class=&quot;token builtin&quot;&gt;uint64&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Global test seed&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	Seed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Now&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;UnixNano&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; envSeed &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; os&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Getenv&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;TEST_SEED&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; envSeed &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		parsed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; strconv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ParseUint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;envSeed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Only set using the env if it&#39;s valid&lt;/span&gt;
			Seed &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; parsed
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	fmt&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Printf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;to reproduce: TEST_SEED=0x%x&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Seed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Rand &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Helper&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token comment&quot;&gt;// We derive a unique seed for each test by XOR-ing the global seed with a hash of the test name.&lt;/span&gt;
	h &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; fnv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New64a&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	perTest &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Sum64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;New&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;rand&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewPCG&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Seed&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;perTest&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Seed&lt;span class=&quot;token operator&quot;&gt;^&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;perTest&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn7&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;7.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I tried to keep the explanation short earlier, so here&#39;s a concrete example. Some of my integration tests share a single, in-memory NATS server. When tests used the same seed, I noticed they would sometimes fail. The reason for this is that multiple tests were accessing the same NATS subjects, even though the tests were meant to be isolated. Using a different seed for each test solves this.&lt;/p&gt;
&lt;p&gt;Also, before the rewrite, each test would create its own NATS server. This is simpler, but it fails when there are a lot of parallel tests. The tests would use up all memory in CI (and sometimes in my local machine) and &lt;a href=&quot;https://en.wikipedia.org/wiki/Out_of_memory&quot;&gt;OOM&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Tests use &lt;code&gt;NewRand&lt;/code&gt; as their source of randomness. Each test also uses a different seed, which is derived from the same original seed. This prevents parallel tests from generating the same random values. While this isn’t a problem for isolated tests, some tests share a common resource, e.g. a server or database. Using the same seed could cause them to pick the same resources, leading to collisions and tests corrupting other tests.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn7&quot; id=&quot;snref7&quot;&gt;[7]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;h2 id=&quot;(almost)-deterministic-simulation-testing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#(almost)-deterministic-simulation-testing&quot;&gt;(Almost) Deterministic Simulation Testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After I finished with the unit tests, I moved on to implement DST. Cardinal wasn’t initially designed with DST in mind, so it took quite a few refactors to get it mostly there. Almost all of the non-deterministic parts are stubbed out, except for the concurrent systems scheduler.&lt;/p&gt;
&lt;aside id=&quot;sn8&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;8.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;It&#39;s technically easy to replace, and I&#39;m also considering replacing it with a simpler, sequential version, but first, I need to do some benchmarks with a lot of different system schedules to understand the implications. It&#39;ll take a while, so I decided to just leave it there so I could get the DST set up first. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;It’s a bit difficult to replace, so I just left it there.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn8&quot; id=&quot;snref8&quot;&gt;[8]&lt;/a&gt;
        &lt;/sup&gt; To guard against scheduler bugs that may not be 100% reproducible, I wrote extra tests to verify the scheduler’s internal concurrency works correctly. This way, I can still have confidence in its behaviour without needing to fully eliminate its non-determinism.&lt;/p&gt;
&lt;aside id=&quot;sn9&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;9.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Which is also my coding agent of choice :) &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I’ve always thought it would be hard to implement DST, considering my first reference is &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/internals/vopr.md&quot;&gt;TigerBeetle’s VOPR&lt;/a&gt;, which has a huge number of components and configurations. Then, I stumbled upon this &lt;a href=&quot;https://wickstrom.tech/2025-08-28-findings-bugs-coding-agent-lightweight-dst.html&quot;&gt;blog post&lt;/a&gt; by Oskar Wickstrom about implementing a lightweight DST for &lt;a href=&quot;https://ampcode.com&quot;&gt;Amp&lt;/a&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn9&quot; id=&quot;fnref9&quot;&gt;[9]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn9&quot; id=&quot;snref9&quot;&gt;[9]&lt;/a&gt;
        &lt;/sup&gt; It showed me that at its core, DST isn’t really a complicated concept, which made DST more attemptable.&lt;/p&gt;
&lt;aside id=&quot;sn10&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;10.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;There&#39;s another way to do DST in Go. This &lt;a href=&quot;https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go&quot;&gt;blog post&lt;/a&gt; by &lt;a href=&quot;https://www.polarsignals.com/&quot;&gt;Polar Signals&lt;/a&gt; explains how they implemented DST in Go by compiling their program to &lt;a href=&quot;https://go.dev/wiki/WebAssembly&quot;&gt;WASM&lt;/a&gt; and (slightly) modifying the Go runtime. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I had the same realisation while writing the model-based tests. They looked a whole lot like the lightweight DST from the blog post. And they are! DST is basically just a bigger model-based test, on the whole system, with non-deterministic components stubbed out. Since I already wrote a lot of them, implementing DST didn’t turn out to be too hard.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn10&quot; id=&quot;fnref10&quot;&gt;[10]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn10&quot; id=&quot;snref10&quot;&gt;[10]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#how-it-works&quot;&gt;How it Works&lt;/a&gt;&lt;/h3&gt;
&lt;aside id=&quot;sn11&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;11.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Here&#39;s &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/42abced153621cc614ac1a1b49a365517fb849d0/src/vopr.zig#L822&quot;&gt;the code&lt;/a&gt;, check for yourself! &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#snref11&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I don’t remember where I first heard this, but TigerBeetle is structured so that it moves in fixed ticks, like a game engine.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fn11&quot; id=&quot;fnref11&quot;&gt;[11]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#sn11&quot; id=&quot;snref11&quot;&gt;[11]&lt;/a&gt;
        &lt;/sup&gt; Time is dependency-injected into the tick, not the other way around like in traditional code. In production use, this uses a regular wall clock, but in simulation, it uses a simulated clock. Well, Cardinal is a tick-based game engine! It was already well-prepared for DST in this regard.&lt;/p&gt;
&lt;p&gt;Cardinal only updates its state on every call to &lt;code&gt;Tick&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/cardinal.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// NOTE: The World type is Cardinal.&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;World&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;Tick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Context&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestamp time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTick&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timestamp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; timestamp

	&lt;span class=&quot;token comment&quot;&gt;// Collect the commands for this tick.&lt;/span&gt;
	&lt;span class=&quot;token boolean&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Drain&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Tick ECS world.&lt;/span&gt;
	w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Tick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Emit events.&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tel&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Warn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Msg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;errors encountered dispatching events&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Publish snapshot every N ticks.&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTick&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;uint64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;SnapshotRate&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		snapshotCtx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancel &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Second&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;snapshot&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;snapshotCtx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token function&quot;&gt;cancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Increment tick height.&lt;/span&gt;
	w&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTick&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;height&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In production use, Cardinal’s &lt;code&gt;StartGame&lt;/code&gt; method handles its lifecycle. It basically runs an infinite loop, calling &lt;code&gt;Tick&lt;/code&gt; every N seconds. For the simulation test, all I had to do was just replace &lt;code&gt;StartGame&lt;/code&gt; and have the test harness control Cardinal using &lt;code&gt;Tick&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here’s a simplified version of the DST harness:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/dst.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;RunDST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setup &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;World&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	prng &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	cfg &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newDSTConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fix &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newDSTFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setup&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Add the world&#39;s commmands as operations in the dst config.&lt;/span&gt;
	cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCommandsAsOperations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Log the test config&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// The main test loop:&lt;/span&gt;
	tick &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; tick &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Ticks &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		op &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RandWeightedOp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;OpWeights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; op &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; opTick&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			timestamp &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Unix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;int64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tick&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Tick&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; timestamp&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;// Assert structural ECS invariants after every tick.&lt;/span&gt;
			ecs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CheckWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			tick&lt;span class=&quot;token operator&quot;&gt;++&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; strings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;HasPrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;op&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opCommandPrefix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			cmdName &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; strings&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;TrimPrefix&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;op&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; opCommandPrefix&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			cmd &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cmdName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;commands&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Enqueue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cmd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; op &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; opRestart&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; op &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; opSnapshotRestore&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;restore&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Final validation after all randomized operations complete.&lt;/span&gt;
	ecs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CheckWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This does a few things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Create a test configuration with random values. There are only a few ATM, and no fancy fault injections, but it’s a start.&lt;/li&gt;
&lt;li&gt;Initialise Cardinal with that configuration and replace non-deterministic components (e.g. NATS handlers, snapshot storage) with deterministic fakes that contain assertions. The non-deterministic parts are tested separately in integration tests.&lt;/li&gt;
&lt;li&gt;Register systems using the user-provided &lt;code&gt;setup&lt;/code&gt; function. I’ll talk more about this soon.&lt;/li&gt;
&lt;li&gt;Extract type information from the command types used in the systems using reflection. We’ll use this to generate random commands to send to the Cardinal.&lt;/li&gt;
&lt;li&gt;Run for N ticks (configurable) and perform a random operation each time.&lt;/li&gt;
&lt;li&gt;We switch between several operations: tick the world, send a random command, reset the world, and restore from a snapshot. The tick operation is always enabled, but the rest are random (as in swarm testing).&lt;/li&gt;
&lt;li&gt;We validate the internal state every tick and at the end of the test.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While implementing this, there was a major design choice I had to make. Cardinal is a framework, and its input is user-provided game logic in the form of systems. I’ll refer to this set of systems as just “game”. The test harness above can only verify Cardinal’s internal invariants. It cannot verify whether the game is logically correct. I had to make a choice between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keeping the harness specific to a single, hand-coded game designed to test Cardinal. This allows us to add more instrumentation to verify some extra invariants and also to validate the game logic, but also means it must be a private type to access the required internal fields.&lt;/li&gt;
&lt;li&gt;Make the harness game-agnostic and public. This gives a few benefits, namely, users can use it with their games, and we also get free inputs to test Cardinal on. We can only verify a subset of all our properties, but we’re more likely to get something we didn’t expect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the end, I went with the second design, betting that this choice would find more bugs through user-provided inputs. I’m hoping that someone somewhere would do something so weird and unpredictable that it crashes the test. Sticking with a single input might cause the harness to be biased.&lt;/p&gt;
&lt;p&gt;So now, we have an importable DST harness that users can use, but not to test their game logic, rather to test that Cardinal works correctly with it. It’s not a substitute for properly testing your game logic. Here’s what its usage looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github.com/argus-labs/world-engine/pkg/cardinal&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;YourTestDST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RunDST&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;World&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token comment&quot;&gt;// Register your systems here...&lt;/span&gt;
		cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RegisterSystem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;PlayerSpawnerSystem&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithHook&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Init&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RegisterSystem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CreatePlayerSystem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RegisterSystem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;RegenSystem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RegisterSystem&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;w&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; system&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;AttackPlayerSystem&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This will run the simulation for 1000 ticks by default. You can configure this using the &lt;code&gt;-dst.ticks&lt;/code&gt; flag.&lt;/p&gt;
&lt;h3 id=&quot;expectations-vs-reality&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#expectations-vs-reality&quot;&gt;Expectations VS Reality&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So initially, I tested with a simple game that I asked AI to write. I was really excited because of all the hype around DST, like how it seemingly found all the bugs in &lt;a href=&quot;https://www.foundationdb.org/&quot;&gt;FoundationDB&lt;/a&gt;. The first time I ran it, the test froze. When I reran it, it froze again! My DST has found its first bug!&lt;/p&gt;
&lt;p&gt;After some investigation, I found out that it was a bug in Cardinal’s scheduler. It’s a deadlock that happens &lt;strong&gt;only when the world is reset after an odd number of ticks&lt;/strong&gt;. A ridiculous bug! I probably would’ve caught this myself in local development, but the fact that the simulator caught it this fast was amazing.&lt;/p&gt;
&lt;p&gt;Then I ran the test again, a lot of times. Each iteration has a different seed and configuration. Nothing. Sometimes I get a bug, but most turned out to be bugs in the harness rather than Cardinal. I tried tuning the test configuration a little bit, but still nothing. At that point, I wasn’t sure what was wrong. Was my harness too simple to find more bugs? Or was my code just bug-free (highly unlikely)? Was it finding bugs, but I didn’t have the correct assertions to catch them?&lt;/p&gt;
&lt;p&gt;I don’t know the answer. And it seems I wasn’t the only one with this issue. The other day, I was watching the &lt;a href=&quot;https://youtu.be/UHdPnubbzBI?si=8Ulo3BXc3x8a5Fr-&quot;&gt;BugBash Podcast episode&lt;/a&gt; with the co-authors of &lt;a href=&quot;https://dataintensive.net/&quot;&gt;DDIA (2nd edition)&lt;/a&gt;, and &lt;a href=&quot;https://cnr.sh/&quot;&gt;Chris Riccomini&lt;/a&gt; talked about his experience adding DST to &lt;a href=&quot;https://slatedb.io/&quot;&gt;SlateDB&lt;/a&gt;. He mentioned things like DST being high-investment but low-return, didn’t explore that much state, and that it only found three known bugs. This matched perfectly with my (short) experience!&lt;/p&gt;
&lt;p&gt;Chris also mentioned that a better approach might be to apply DST on subcomponents much earlier in the process instead of going all in with full-system DST, which would’ve given more value much earlier. Coincidentally, this is what I did (if you count the model-based tests as DST), and it also aligns with my experience. I had caught several bugs from those tests. It’s probably why my DST didn’t catch many bugs, but that’s just my wishful thinking.&lt;/p&gt;
&lt;p&gt;I’ll also add this quote from &lt;a href=&quot;https://antithesis.com/author/will-wilson/&quot;&gt;Will Wilson&lt;/a&gt;, the CEO of Antithesis:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The biggest challenge of DST in my experience is that tuning all the random distributions, the parameters of your system, the workload, the fault injection, etc. so that it produces interesting behaviour is very challenging and very labor intensive. As with fuzzing or PBT, it’s terrifyingly easy to build a DST system that appears to be doing a ton of testing, but actually never explores very much of the state space of your system. At FoundationDB, the vast majority of the work we put into the simulator was an iterative process of hunting for what wasn’t being covered by our tests and then figuring out how to make the tests better. This process often resembles science more than it does engineering.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I’m still not giving up, though. My DST harness is still small, which means it can only grow from here. I already have several improvements in mind, so I’ll stick with it for a bit longer.&lt;/p&gt;
&lt;h2 id=&quot;end-to-end-fuzzing&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#end-to-end-fuzzing&quot;&gt;End-to-End Fuzzing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The final test I added was the E2E tests. This actually followed a similar structure to the DST, where we set up Cardinal with user-provided game logic and bombard it with randomised commands. The biggest difference is that it runs with the same, non-deterministic components as it would in production.&lt;/p&gt;
&lt;p&gt;The actual harness code is pretty gnarly, so here’s an extremely simplified version of it:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;pkg/cardinal/e2e.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;RunE2E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setup E2ESetupFunc&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	prng &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewRand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	cfg &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newE2EConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	fix &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;newE2EFixture&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; setup&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Add the world&#39;s commmands as operations in the dst config.&lt;/span&gt;
	cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addCommandsAsOperations&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Log the test config&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Start the world&#39;s run loop in a background goroutine.&lt;/span&gt;
	ctx&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cancel &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;WithCancel&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Background&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	runErr &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;chan&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		runErr &lt;span class=&quot;token operator&quot;&gt;&amp;lt;-&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;ctx&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

	deadline &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; time&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;After&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Duration&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
sendLoop&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;-&lt;/span&gt;deadline&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;stopWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;nil&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;errors&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Is&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Canceled&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
				t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Fatalf&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;world.run returned unexpected error: %v&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt; sendLoop
		&lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
			name &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; testutils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RandWeightedOp&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cfg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;CommandWeights&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			cmd &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; prng&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
			fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sendCommand&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cmd&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

	&lt;span class=&quot;token comment&quot;&gt;// Final validation after the world has fully stopped.&lt;/span&gt;
	ecs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;CheckWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fix&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;world&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;All this does is run Cardinal in a separate goroutine and send it randomised requests. Unlike in DST, where you control the number of ticks to run, here you control the test duration. Since it uses wall clock time, it’ll run much slower than other tests. This is why I made the E2E tests disabled by default. To run them, pass the &lt;code&gt;-e2e.run&lt;/code&gt; flag to &lt;code&gt;go test&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The usage API is also similar to DST, but instead of passing a function to register systems, you’d return the actual &lt;code&gt;cardinal.World&lt;/code&gt; instead. In DST, we randomise Cardinal’s config, but in the E2E test, we want it to have the same config as production to test more realistically:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example_test.go&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-go&quot;&gt;&lt;code class=&quot;language-go&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;github.com/argus-labs/world-engine/pkg/cardinal&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TestE2E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;testing&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;T&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
	cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;RunE2E&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;World &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
		world&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err &lt;span class=&quot;token operator&quot;&gt;:=&lt;/span&gt; cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NewWorld&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cardinal&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;WorldOptions&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;token comment&quot;&gt;// Your world configuration...&lt;/span&gt;
		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
		require&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;NoError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;t&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

		&lt;span class=&quot;token comment&quot;&gt;// Register systems here...&lt;/span&gt;

		&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; world
	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;what%E2%80%99s-next%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/generative-testing/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Phew… That was long. I had cut out a lot of things, including a section on testing concurrent code, but I still ended up with around 7000 words. If you read the entire thing, thank you and big props to you.&lt;/p&gt;
&lt;p&gt;Here’s the &lt;a href=&quot;https://github.com/Argus-Labs/world-engine/tree/ddefa5b7cc9992e5db41a5d181520d7e2f87f0db&quot;&gt;source code&lt;/a&gt; if you want to take a closer look. After all this, I’m even more excited about software correctness. There are so many more things I want to try, e.g. improve my DST harness, test using a &lt;a href=&quot;https://antithesis.com/blog/deterministic_hypervisor/&quot;&gt;deterministic hypervisor&lt;/a&gt; :), formal methods, etc.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/generative-testing/q6JJPjYOAk-1080.webp&quot; alt=&quot;Big plans for this code beaver meme&quot; width=&quot;1080&quot; height=&quot;1075&quot;&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep asterism&quot;&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Only the tests were slop, I actually put a lot of thought into the code itself! &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I like comparing PBT and fuzzing to &lt;a href=&quot;https://en.wikipedia.org/wiki/Convergent_evolution&quot;&gt;convergent evolution&lt;/a&gt;, which is when species of different lineages evolve similar features. PBT came from functional programming, fuzzing came much earlier from systems/security. The approach is similar, but their focus is different (program correctness vs security issues). Yes, I got this from &lt;a href=&quot;https://antithesis.com/product/how_is_antithesis_different/#property-based-testing-vs.-antithesis&quot;&gt;Antithesis&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I recommend reading &lt;a href=&quot;https://antithesis.com/resources/property_based_testing/&quot;&gt;Antithesis’s PBT primer&lt;/a&gt; first, and then &lt;a href=&quot;https://fsharpforfunandprofit.com/series/property-based-testing/&quot;&gt;F# for Fun and Profit’s PBT series&lt;/a&gt; for some more practical tips. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;My real reason for adopting DST is just because I think it’s cool. It’s what got me interested in TigerBeetle in the first place. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There are some approaches that address this problem (somewhat), e.g. &lt;a href=&quot;https://en.wikipedia.org/wiki/metamorphic_testing&quot;&gt;metamorphic testing&lt;/a&gt;. It solves this by using oracles derived from the tests themselves (through special properties called metamorphic relations), but it’s not a universal solution. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This is probably why shrinking in pbt works. While the original intent is to create a simpler representation for debugging, the fact remains that the same bugs found in larger inputs can usually be reproduced in smaller ones. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I tried to keep the explanation short earlier, so here’s a concrete example. Some of my integration tests share a single, in-memory NATS server. When tests used the same seed, I noticed they would sometimes fail. The reason for this is that multiple tests were accessing the same NATS subjects, even though the tests were meant to be isolated. Using a different seed for each test solves this.&lt;/p&gt;
&lt;p&gt;Also, before the rewrite, each test would create its own NATS server. This is simpler, but it fails when there are a lot of parallel tests. The tests would use up all memory in CI (and sometimes in my local machine) and &lt;a href=&quot;https://en.wikipedia.org/wiki/Out_of_memory&quot;&gt;OOM&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;It’s technically easy to replace, and I’m also considering replacing it with a simpler, sequential version, but first, I need to do some benchmarks with a lot of different system schedules to understand the implications. It’ll take a while, so I decided to just leave it there so I could get the DST set up first. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn9&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Which is also my coding agent of choice :) &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn10&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There’s another way to do DST in Go. This &lt;a href=&quot;https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go&quot;&gt;blog post&lt;/a&gt; by &lt;a href=&quot;https://www.polarsignals.com/&quot;&gt;Polar Signals&lt;/a&gt; explains how they implemented DST in Go by compiling their program to &lt;a href=&quot;https://go.dev/wiki/WebAssembly&quot;&gt;WASM&lt;/a&gt; and (slightly) modifying the Go runtime. &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn11&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Here’s &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/42abced153621cc614ac1a1b49a365517fb849d0/src/vopr.zig#L822&quot;&gt;the code&lt;/a&gt;, check for yourself! &lt;a href=&quot;https://ryanmartin.me/articles/generative-testing/#fnref11&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;svg style=&quot;display: none;&quot; id=&quot;MJX-SVG-global-cache&quot;&gt;&lt;defs&gt;&lt;path id=&quot;MJX-TEX-I-1D441&quot; d=&quot;M234 637Q231 637 226 637Q201 637 196 638T191 649Q191 676 202 682Q204 683 299 683Q376 683 387 683T401 677Q612 181 616 168L670 381Q723 592 723 606Q723 633 659 637Q635 637 635 648Q635 650 637 660Q641 676 643 679T653 683Q656 683 684 682T767 680Q817 680 843 681T873 682Q888 682 888 672Q888 650 880 642Q878 637 858 637Q787 633 769 597L620 7Q618 0 599 0Q585 0 582 2Q579 5 453 305L326 604L261 344Q196 88 196 79Q201 46 268 46H278Q284 41 284 38T282 19Q278 6 272 0H259Q228 2 151 2Q123 2 100 2T63 2T46 1Q31 1 31 10Q31 14 34 26T39 40Q41 46 62 46Q130 49 150 85Q154 91 221 362L289 634Q287 635 234 637Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-32&quot; d=&quot;M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-33&quot; d=&quot;M127 463Q100 463 85 480T69 524Q69 579 117 622T233 665Q268 665 277 664Q351 652 390 611T430 522Q430 470 396 421T302 350L299 348Q299 347 308 345T337 336T375 315Q457 262 457 175Q457 96 395 37T238 -22Q158 -22 100 21T42 130Q42 158 60 175T105 193Q133 193 151 175T169 130Q169 119 166 110T159 94T148 82T136 74T126 70T118 67L114 66Q165 21 238 21Q293 21 321 74Q338 107 338 175V195Q338 290 274 322Q259 328 213 329L171 330L168 332Q166 335 166 348Q166 366 174 366Q202 366 232 371Q266 376 294 413T322 525V533Q322 590 287 612Q265 626 240 626Q208 626 181 615T143 592T132 580H135Q138 579 143 578T153 573T165 566T175 555T183 540T186 520Q186 498 172 481T127 463Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-I-D7&quot; d=&quot;M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-36&quot; d=&quot;M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-34&quot; d=&quot;M462 0Q444 3 333 3Q217 3 199 0H190V46H221Q241 46 248 46T265 48T279 53T286 61Q287 63 287 115V165H28V211L179 442Q332 674 334 675Q336 677 355 677H373L379 671V211H471V165H379V114Q379 73 379 66T385 54Q393 47 442 46H471V0H462ZM293 211V545L74 212L183 211H293Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-3D&quot; d=&quot;M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-30&quot; d=&quot;M96 585Q152 666 249 666Q297 666 345 640T423 548Q460 465 460 320Q460 165 417 83Q397 41 362 16T301 -15T250 -22Q224 -22 198 -16T137 16T82 83Q39 165 39 320Q39 494 96 585ZM321 597Q291 629 250 629Q208 629 178 597Q153 571 145 525T137 333Q137 175 145 125T181 46Q209 16 250 16Q290 16 318 46Q347 76 354 130T362 333Q362 478 354 524T321 597Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-39&quot; d=&quot;M352 287Q304 211 232 211Q154 211 104 270T44 396Q42 412 42 436V444Q42 537 111 606Q171 666 243 666Q245 666 249 666T257 665H261Q273 665 286 663T323 651T370 619T413 560Q456 472 456 334Q456 194 396 97Q361 41 312 10T208 -22Q147 -22 108 7T68 93T121 149Q143 149 158 135T173 96Q173 78 164 65T148 49T135 44L131 43Q131 41 138 37T164 27T206 22H212Q272 22 313 86Q352 142 352 280V287ZM244 248Q292 248 321 297T351 430Q351 508 343 542Q341 552 337 562T323 588T293 615T246 625Q208 625 181 598Q160 576 154 546T147 441Q147 358 152 329T172 282Q197 248 244 248Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-31&quot; d=&quot;M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-2C&quot; d=&quot;M78 35T78 60T94 103T137 121Q165 121 187 96T210 8Q210 -27 201 -60T180 -117T154 -158T130 -185T117 -194Q113 -194 104 -185T95 -172Q95 -168 106 -156T131 -126T157 -76T173 -3V9L172 8Q170 7 167 6T161 3T152 1T140 0Q113 0 96 17Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-35&quot; d=&quot;M164 157Q164 133 148 117T109 101H102Q148 22 224 22Q294 22 326 82Q345 115 345 210Q345 313 318 349Q292 382 260 382H254Q176 382 136 314Q132 307 129 306T114 304Q97 304 95 310Q93 314 93 485V614Q93 664 98 664Q100 666 102 666Q103 666 123 658T178 642T253 634Q324 634 389 662Q397 666 402 666Q410 666 410 648V635Q328 538 205 538Q174 538 149 544L139 546V374Q158 388 169 396T205 412T256 420Q337 420 393 355T449 201Q449 109 385 44T229 -22Q148 -22 99 32T50 154Q50 178 61 192T84 210T107 214Q132 214 148 197T164 157Z&quot;&gt;&lt;/path&gt;&lt;/defs&gt;&lt;/svg&gt;&lt;/body&gt;&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Build and Deploy Web Apps With Clojure and FLy.io</title>
    <link href="https://ryanmartin.me/articles/clojure-fly/" />
    <updated>2026-04-04T12:25:25Z</updated>
    <id>https://ryanmartin.me/articles/clojure-fly/</id>
    <content type="html">
&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;This post walks through a small web development project using Clojure, covering everything from building the app to packaging and deploying it. It’s a collection of insights and tips I’ve learned from building my Clojure side projects, but presented in a more structured format.&lt;/p&gt;
&lt;aside id=&quot;sn1&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;1.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;The way Fly.io works under the hood is pretty clever. Instead of running the container image with a runtime like Docker, the image is unpacked and &quot;loaded&quot; into a VM. See &lt;a href=&quot;https://www.youtube.com/watch?v=7iypMRKniPU&quot;&gt;this video explanation&lt;/a&gt; for more details. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;As the title suggests, we’ll be deploying the app to &lt;a href=&quot;https://fly.io&quot;&gt;Fly.io&lt;/a&gt;. It’s a service that allows you to deploy apps packaged as Docker images on lightweight virtual machines.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn1&quot; id=&quot;snref1&quot;&gt;[1]&lt;/a&gt;
        &lt;/sup&gt; My experience with it has been good; it’s easy to use and quick to set up. One downside of Fly is that it doesn’t have a free tier, but if you don’t plan on leaving the app deployed, it barely costs anything.&lt;/p&gt;
&lt;aside id=&quot;sn2&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;2.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;If you&#39;re interested in learning Clojure, my recommendation is to follow &lt;a href=&quot;https://clojure.org/guides/getting_started&quot;&gt;the official getting started guide&lt;/a&gt; and join the &lt;a href=&quot;https://clojurians.slack.com/&quot;&gt;Clojurians Slack&lt;/a&gt;. Also, read through this &lt;a href=&quot;https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f&quot;&gt;list of introductory resources&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This isn’t a tutorial on Clojure, so I’ll assume you already have some familiarity with the language as well as some of its libraries.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn2&quot; id=&quot;snref2&quot;&gt;[2]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;aside class=&quot;toc&quot;&gt;&lt;div&gt;
      &lt;h2 id=&quot;toc&quot;&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#toc&quot; class=&quot;header-anchor&quot;&gt;Table of Contents&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#systems-and-configuration&quot;&gt;Systems and Configuration&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#routing%2C-middleware%2C-and-route-handlers&quot;&gt;Routing, Middleware, and Route Handlers&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#implementing-the-middlewares&quot;&gt;Implementing the Middlewares&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#implementing-the-route-handlers&quot;&gt;Implementing the Route Handlers&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#packaging-the-app&quot;&gt;Packaging the App&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#deploying-with-fly.io&quot;&gt;Deploying with Fly.io&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#adding-a-production-repl&quot;&gt;Adding a Production REPL&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#deploy-with-github-actions&quot;&gt;Deploy with GitHub Actions&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#end&quot;&gt;End&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;project-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In this post, we’ll be building a barebones bookmarks manager for the demo app. Users can log in using &lt;a href=&quot;https://roadmap.sh/guides/http-basic-authentication&quot;&gt;basic authentication&lt;/a&gt;, view all bookmarks, and create a new bookmark. It’ll be a traditional multi-page web app and the data will be stored in a &lt;a href=&quot;https://sqlite.org&quot;&gt;SQLite&lt;/a&gt; database.&lt;/p&gt;
&lt;p&gt;Here’s an overview of the project’s starting directory structure:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;.
├── dev
│   └── user.clj
├── resources
│   └── config.edn
├── src
│   └── acme
│       └── main.clj
└── deps.edn&lt;/code&gt;&lt;/pre&gt;
&lt;aside id=&quot;sn3&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;3.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Kit was a big influence on me when I first started learning web development in Clojure. I never used it directly, but I did use their library choices and project structure as a base for my own projects. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;And the libraries we’re going to use. If you have some Clojure experience or have used &lt;a href=&quot;https://kit-clj.github.io/&quot;&gt;Kit&lt;/a&gt;, you’re probably already familiar with all the libraries listed below.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn3&quot; id=&quot;snref3&quot;&gt;[3]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;deps.edn&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:paths&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
 &lt;span class=&quot;token symbol&quot;&gt;:deps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;org.clojure/clojure               &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.12.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        aero/aero                         &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.1.6&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        integrant/integrant               &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.11.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        ring/ring-jetty-adapter           &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.12.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        metosin/reitit-ring               &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.7.2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        com.github.seancorfield/next.jdbc &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.3.939&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        org.xerial/sqlite-jdbc            &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;3.46.1.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        hiccup/hiccup                     &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2.0.0-RC3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
 &lt;span class=&quot;token symbol&quot;&gt;:aliases&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:dev&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:extra-paths&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;dev&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token symbol&quot;&gt;:extra-deps&lt;/span&gt;  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;nrepl/nrepl    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.3.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
                      integrant/repl &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:mvn/version&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.3.3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;token symbol&quot;&gt;:main-opts&lt;/span&gt;   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;-m&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;nrepl.cmdline&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--interactive&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;--color&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn4&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;4.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;There&#39;s no &quot;Rails&quot; for the Clojure ecosystem (yet?). The prevailing opinion is to build your own &quot;framework&quot; by composing different libraries together. Most of these libraries are stable and are already used in production by big companies, so don&#39;t let this discourage you from doing web development in Clojure! &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;I use &lt;a href=&quot;https://github.com/juxt/aero&quot;&gt;Aero&lt;/a&gt; and &lt;a href=&quot;https://github.com/weavejester/integrant&quot;&gt;Integrant&lt;/a&gt; for my system configuration (more on this in the next section), &lt;a href=&quot;https://github.com/ring-clojure/ring&quot;&gt;Ring&lt;/a&gt; with the Jetty adaptor for the web server, &lt;a href=&quot;https://github.com/metosin/reitit&quot;&gt;Reitit&lt;/a&gt; for routing, &lt;a href=&quot;https://github.com/seancorfield/next-jdbc/&quot;&gt;next.jdbc&lt;/a&gt; for database interaction, and &lt;a href=&quot;https://github.com/weavejester/hiccup/&quot;&gt;Hiccup&lt;/a&gt; for rendering HTML. From what I’ve seen, this is a popular “library combination” for building web apps in Clojure.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn4&quot; id=&quot;snref4&quot;&gt;[4]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;user&lt;/code&gt; namespace in &lt;code&gt;dev/user.clj&lt;/code&gt; contains helper functions from &lt;a href=&quot;https://github.com/weavejester/integrant-repl&quot;&gt;Integrant-repl&lt;/a&gt; to start, stop, and restart the Integrant system.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;dev/user.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; user
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acme.main &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; main&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;clojure.tools.namespace.repl &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; repl&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;integrant.core &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; ig&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;integrant.repl &lt;span class=&quot;token symbol&quot;&gt;:refer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;set-prep! go halt reset reset-all&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set-prep!&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ig/expand&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;main/read-config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;;; we&#39;ll implement this soon&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;repl/set-refresh-dirs&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comment&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;go&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;halt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reset-all&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;systems-and-configuration&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#systems-and-configuration&quot;&gt;Systems and Configuration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you’re new to Integrant or other dependency injection libraries like &lt;a href=&quot;https://github.com/stuartsierra/component&quot;&gt;Component&lt;/a&gt;, I’d suggest reading &lt;a href=&quot;https://mccue.dev/pages/12-7-22-clojure-web-primer&quot;&gt;“How to Structure a Clojure Web”&lt;/a&gt;. It’s a great explanation of the reasoning behind these libraries. Like most Clojure apps that use Aero and Integrant, my system configuration lives in a &lt;code&gt;.edn&lt;/code&gt; file. I usually name mine as &lt;code&gt;resources/config.edn&lt;/code&gt;. Here’s what it looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;resources/config.edn&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:server&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:port&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;long &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;or &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;env PORT &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;:host&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;or &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;env HOST &lt;span class=&quot;token string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;:auth&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:username&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;or &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;env AUTH_USER &lt;span class=&quot;token string&quot;&gt;&quot;john.doe@email.com&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
         &lt;span class=&quot;token symbol&quot;&gt;:password&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;or &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;env AUTH_PASSWORD &lt;span class=&quot;token string&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

 &lt;span class=&quot;token symbol&quot;&gt;:database&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:dbtype&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;sqlite&quot;&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;:dbname&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;or &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;env DB_DATABASE &lt;span class=&quot;token string&quot;&gt;&quot;database.db&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In production, most of these values will be set using environment variables. During local development, the app will use the hard-coded default values. We don’t have any sensitive values in our config (e.g., API keys), so it’s fine to commit this file to version control. If there are such values, I usually put them in another file that’s not tracked by version control and include them in the config file using Aero’s &lt;code&gt;#include&lt;/code&gt; reader tag.&lt;/p&gt;
&lt;p&gt;This config file is then “expanded” into the Integrant system map using the &lt;code&gt;expand-key&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/main.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.main
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;aero.core &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; aero&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;clojure.java.io &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; io&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;integrant.core &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; ig&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;read-config&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:system/config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;aero/read-config&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;io/resource&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;config.edn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;ig/expand-key&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:system/config&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_ opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;server database&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:server/jetty&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;assoc&lt;/span&gt; server &lt;span class=&quot;token symbol&quot;&gt;:handler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ig/ref&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:handler/ring&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
     &lt;span class=&quot;token symbol&quot;&gt;:handler/ring&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:database&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ig/ref&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:database/sql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                    &lt;span class=&quot;token symbol&quot;&gt;:auth&lt;/span&gt;     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:auth&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;token symbol&quot;&gt;:database/sql&lt;/span&gt; database&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn5&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;5.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;There might be some keys that you add or remove, but the structure of the config file stays the same. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;The system map is created in code instead of being in the configuration file. This makes refactoring your system simpler as you only need to change this method while leaving the config file (mostly) untouched.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn5&quot; id=&quot;snref5&quot;&gt;[5]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;My current approach to Integrant + Aero config files is mostly inspired by the blog post &lt;a href=&quot;https://robjohnson.dev/posts/aero-and-integrant/&quot;&gt;“Rethinking Config with Aero &amp;amp; Integrant”&lt;/a&gt; and Laravel’s configuration. The config file follows a similar structure to Laravel’s config files and contains the app configurations without describing the structure of the system. Previously, I had a key for each Integrant component, which led to the config file being littered with &lt;code&gt;#ig/ref&lt;/code&gt; and more difficult to refactor.&lt;/p&gt;
&lt;p&gt;Also, if you haven’t already, start a REPL and connect to it from your editor. Run &lt;code&gt;clj -M:dev&lt;/code&gt; if your editor doesn’t automatically start a REPL. Next, we’ll implement the &lt;code&gt;init-key&lt;/code&gt; and &lt;code&gt;halt-key!&lt;/code&gt; methods for each of the components:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/main.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; src/acme/main.clj&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.main
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acme.handler &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; handler&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acme.util &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; util&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;next.jdbc &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; jdbc&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ring.adapter.jetty &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; jetty&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;ig/init-key&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:server/jetty&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_ opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;handler port&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; opts
        jetty-opts &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;-&amp;gt;&lt;/span&gt; opts &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;dissoc&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:handler&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:auth&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;assoc&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:join?&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        server     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;jetty/run-jetty&lt;/span&gt; handler jetty-opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Server started on port &quot;&lt;/span&gt; port&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;ig/halt-key!&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:server/jetty&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_ server&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;.stop&lt;/span&gt; server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;ig/init-key&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:handler/ring&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_ opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handler/handler&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defmethod&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;ig/init-key&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:database/sql&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_ opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;datasource &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;jdbc/get-datasource&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;util/setup-db&lt;/span&gt; datasource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    datasource&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;setup-db&lt;/code&gt; function creates the required tables in the database if they don’t exist yet. This works fine for database migrations in small projects like this demo app, but for larger projects, consider using libraries such as &lt;a href=&quot;https://github.com/yogthos/migratus&quot;&gt;Migratus&lt;/a&gt; (my preferred library) or &lt;a href=&quot;https://github.com/weavejester/ragtime&quot;&gt;Ragtime&lt;/a&gt;.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/util.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.util 
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;next.jdbc &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; jdbc&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;setup-db&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;db&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;jdbc/execute-one!&lt;/span&gt;
   db
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;create table if not exists bookmarks (
       bookmark_id text primary key not null,
       url text not null,
       created_at datetime default (unixepoch()) not null
     )&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;For the server handler, let’s start with a simple function that returns a “hi world” string.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.handler
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ring.util.response &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;handler&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/response&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;hi world&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now all the components are implemented. We can check if the system is working properly by evaluating &lt;code&gt;(reset)&lt;/code&gt; in the &lt;code&gt;user&lt;/code&gt; namespace. This will reload your files and restart the system. You should see this message printed in your REPL:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;:reloading (acme.util acme.handler acme.main)
Server started on port  8080
:resumed&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If we send a request to &lt;code&gt;http://localhost:8080/&lt;/code&gt;, we should get “hi world” as the response:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;curl&lt;/span&gt; localhost:8080/
&lt;span class=&quot;token comment&quot;&gt;# hi world&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Nice! The system is working correctly. In the next section, we’ll implement routing and our business logic handlers.&lt;/p&gt;
&lt;h2 id=&quot;routing%2C-middleware%2C-and-route-handlers&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#routing%2C-middleware%2C-and-route-handlers&quot;&gt;Routing, Middleware, and Route Handlers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, let’s set up a ring handler and router using Reitit. We only have one route, the index &lt;code&gt;/&lt;/code&gt; route that’ll handle both GET and POST requests.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.handler
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;reitit.ring &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; ring&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;routes&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:get&lt;/span&gt;  index-page
         &lt;span class=&quot;token symbol&quot;&gt;:post&lt;/span&gt; index-action&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;handler&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/ring-handler&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/router&lt;/span&gt; routes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/routes&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/redirect-trailing-slash-handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/create-resource-handler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:path&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/create-default-handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We’re including some useful middleware:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;redirect-trailing-slash-handler&lt;/code&gt; to resolve routes with trailing slashes,&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create-resource-handler&lt;/code&gt; to serve static files, and&lt;/li&gt;
&lt;li&gt;&lt;code&gt;create-default-handler&lt;/code&gt; to handle common 40x responses.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;implementing-the-middlewares&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#implementing-the-middlewares&quot;&gt;Implementing the Middlewares&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you remember the &lt;code&gt;:handler/ring&lt;/code&gt; from earlier, you’ll notice that it has two dependencies, &lt;code&gt;database&lt;/code&gt; and &lt;code&gt;auth&lt;/code&gt;. Currently, they’re inaccessible to our route handlers. To fix this, we can inject these components into the Ring request map using a middleware function.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;components-middleware&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;components&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;database auth&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; components&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;assoc&lt;/span&gt; req
                        &lt;span class=&quot;token symbol&quot;&gt;:db&lt;/span&gt; database
                        &lt;span class=&quot;token symbol&quot;&gt;:auth&lt;/span&gt; auth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn6&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;6.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;&quot;assoc&quot; (associate) is a Clojure slang that means to add or update a key-value pair in a map. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;The &lt;code&gt;components-middleware&lt;/code&gt; function takes in a map of components and creates a middleware function that “assocs” each component into the request map.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn6&quot; id=&quot;snref6&quot;&gt;[6]&lt;/a&gt;
        &lt;/sup&gt; If you have more components such as a Redis cache or a mail service, you can add them here.&lt;/p&gt;
&lt;aside id=&quot;sn7&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;7.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;For more details on how basic authentication works, check out &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7617.html&quot;&gt;the specification&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;We’ll also need a middleware to handle HTTP basic authentication.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn7&quot; id=&quot;snref7&quot;&gt;[7]&lt;/a&gt;
        &lt;/sup&gt; This middleware will check if the username and password from the request map match the values in the &lt;code&gt;auth&lt;/code&gt; map injected by &lt;code&gt;components-middleware&lt;/code&gt;. If they match, then the request is authenticated and the user can view the site.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.handler
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acme.util &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; util&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ring.util.response &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;wrap-basic-auth&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;handler&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;headers auth&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; req
          &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;username password&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; auth
          authorization &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; headers &lt;span class=&quot;token string&quot;&gt;&quot;authorization&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          correct-creds &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Basic &quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;util/base64-encode&lt;/span&gt;
                                       &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;format&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;%s:%s&quot;&lt;/span&gt; username password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; authorization &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;=&lt;/span&gt; correct-creds authorization&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/response&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Access Denied&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/status&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;401&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/header&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;WWW-Authenticate&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Basic realm=protected&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;A nice feature of Clojure is that interop with the host language is easy. The &lt;code&gt;base64-encode&lt;/code&gt; function is just a thin wrapper over Java’s &lt;code&gt;Base64.Encoder&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/util.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.util
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:import&lt;/span&gt; java.util.Base64&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;base64-encode&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;.encodeToString&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;Base64/getEncoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;.getBytes&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Finally, we need to add them to the router. Since we’ll be handling form requests later, we’ll also bring in Ring’s &lt;code&gt;wrap-params&lt;/code&gt; middleware.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.handler
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ring.middleware.params &lt;span class=&quot;token symbol&quot;&gt;:refer&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;wrap-params&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;handler&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;opts&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;ring/ring-handler&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:middleware&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;components-middleware&lt;/span&gt; opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                 wrap-basic-auth
                 wrap-params&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h3 id=&quot;implementing-the-route-handlers&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#implementing-the-route-handlers&quot;&gt;Implementing the Route Handlers&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We now have everything we need to implement the route handlers or the business logic of the app. First, we’ll implement the &lt;code&gt;index-page&lt;/code&gt; function, which renders a page that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shows all of the user’s bookmarks in the database, and&lt;/li&gt;
&lt;li&gt;Shows a form that allows the user to insert new bookmarks into the database&lt;/li&gt;
&lt;/ol&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.handler
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;next.jdbc &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; jdbc&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;next.jdbc.sql &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; sql&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;template&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;bookmarks&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:html&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:head&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:meta&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:charset&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;utf-8&quot;&lt;/span&gt;
            &lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt;    &lt;span class=&quot;token string&quot;&gt;&quot;viewport&quot;&lt;/span&gt;
            &lt;span class=&quot;token symbol&quot;&gt;:content&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;width=device-width, initial-scale=1.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:body&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:h1&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;bookmarks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:form&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:method&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;POST&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:div&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:label&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:for&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;url &quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:input&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;#&lt;/span&gt;url &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:name&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;url&quot;&lt;/span&gt;
                   &lt;span class=&quot;token symbol&quot;&gt;:type&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;url&quot;&lt;/span&gt;
                   &lt;span class=&quot;token symbol&quot;&gt;:required&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
                   &lt;span class=&quot;token symbol&quot;&gt;:placeholer&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://en.wikipedia.org/&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:button&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;submit&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:p&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;your bookmarks:&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:ul&lt;/span&gt;
     &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;empty?&lt;/span&gt; bookmarks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:li&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;you don&#39;t have any bookmarks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
       &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;map&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:li&lt;/span&gt;
           &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:a&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:href&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        bookmarks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;index-page&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;bookmarks &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sql/query&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:db&lt;/span&gt; req&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
                               &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;select * from bookmarks&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                               jdbc/unqualified-snake-kebab-opts&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;util/render&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;template&lt;/span&gt; bookmarks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt; Exception e
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;util/server-error&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Database queries can sometimes throw exceptions, so it’s good to wrap them in a try-catch block. I’ll also introduce some helper functions:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/util.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.util
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;hiccup2.core &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; h&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;ring.util.response &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:import&lt;/span&gt; java.util.Base64&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;preprend-doctype&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;s&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;str&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&amp;lt;!doctype html&amp;gt;&quot;&lt;/span&gt; s&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;render&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;hiccup&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;-&amp;gt;&lt;/span&gt; hiccup h/html str preprend-doctype res/response &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/content-type&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;server-error&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Caught exception: &quot;&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/response&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Internal server error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/status&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;render&lt;/code&gt; takes a hiccup form and turns it into a ring response, while &lt;code&gt;server-error&lt;/code&gt; takes an exception, logs it, and returns a 500 response.&lt;/p&gt;
&lt;p&gt;Next, we’ll implement the &lt;code&gt;index-action&lt;/code&gt; function:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/handler.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;index-action&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:keys&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;db form-params&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; req
          value &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; form-params &lt;span class=&quot;token string&quot;&gt;&quot;url&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;sql/insert!&lt;/span&gt; db &lt;span class=&quot;token symbol&quot;&gt;:bookmarks&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:bookmark_id&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;random-uuid&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:url&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;res/redirect&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/&quot;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;303&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt; Exception e
      &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;util/server-error&lt;/span&gt; e&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is an implementation of a typical &lt;a href=&quot;https://en.wikipedia.org/wiki/Post/Redirect/Get&quot;&gt;post/redirect/get&lt;/a&gt; pattern. We get the value from the URL form field, insert a new row in the database with that value, and redirect back to the index page. Again, we’re using a try-catch block to handle possible exceptions from the database query.&lt;/p&gt;
&lt;p&gt;That should be all of the code for the controllers. If you reload your REPL and go to &lt;code&gt;http://localhost:8080&lt;/code&gt;, you should see something that looks like this after logging in:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/clojure-fly/YmhzvGHPKu-1214.webp&quot; alt=&quot;Screnshot of the app&quot; width=&quot;1214&quot; height=&quot;737&quot;&gt;&lt;/p&gt;
&lt;p&gt;The last thing we need to do is to update the main function to start the system:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/main.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;-main&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&amp;amp; _&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;-&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;read-config&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; ig/expand ig/init&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now, you should be able to run the app using &lt;code&gt;clj -M -m acme.main&lt;/code&gt;. That’s all the code needed for the app. In the next section, we’ll package the app into a Docker image to deploy to Fly.&lt;/p&gt;
&lt;h2 id=&quot;packaging-the-app&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#packaging-the-app&quot;&gt;Packaging the App&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While there are &lt;a href=&quot;https://www.metosin.fi/blog/packaging-clojure&quot;&gt;many ways to package a Clojure app&lt;/a&gt;, Fly.io specifically requires a Docker image. There are two approaches to doing this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Build an uberjar and run it using Java in the container, or&lt;/li&gt;
&lt;li&gt;Load the source code and run it using Clojure in the container&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both are valid approaches. I prefer the first since its only dependency is the JVM. We’ll use the &lt;a href=&quot;https://github.com/clojure/tools.build&quot;&gt;tools.build&lt;/a&gt; library to build the uberjar. Check out the &lt;a href=&quot;https://clojure.org/guides/tools_build&quot;&gt;official guide&lt;/a&gt; for more information on building Clojure programs. Since it’s a library, to use it, we can add it to our &lt;code&gt;deps.edn&lt;/code&gt; file with an alias:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;deps.edn&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
 &lt;span class=&quot;token symbol&quot;&gt;:aliases&lt;/span&gt;
 &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
  &lt;span class=&quot;token symbol&quot;&gt;:build&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:extra-deps&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;io.github.clojure/tools.build 
                       &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:git/tag&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;v0.10.5&quot;&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:git/sha&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;2a21b7a&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
          &lt;span class=&quot;token symbol&quot;&gt;:ns-default&lt;/span&gt; build&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Tools.build expects a &lt;code&gt;build.clj&lt;/code&gt; file in the root of the project directory, so we’ll need to create that file. This file contains the instructions to build artefacts, which in our case is a single uberjar. There are many great examples of &lt;code&gt;build.clj&lt;/code&gt; files on the web, including from the official documentation. For now, you can copy+paste this file into your project.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;build.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; build
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:require&lt;/span&gt;
   &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;clojure.tools.build.api &lt;span class=&quot;token symbol&quot;&gt;:as&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;basis&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;delay&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;b/create-basis&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:project&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;deps.edn&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;src-dirs&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;resources&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;class-dir&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/classes&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;defn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;uber&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Cleaning build directory...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;b/delete&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:path&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Copying files...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;b/copy-dir&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:src-dirs&lt;/span&gt;   src-dirs
               &lt;span class=&quot;token symbol&quot;&gt;:target-dir&lt;/span&gt; class-dir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Compiling Clojure...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;b/compile-clj&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:basis&lt;/span&gt;      &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;basis
                  &lt;span class=&quot;token symbol&quot;&gt;:ns-compile&lt;/span&gt; &#39;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;acme.main&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
                  &lt;span class=&quot;token symbol&quot;&gt;:class-dir&lt;/span&gt;  class-dir&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;println&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Building Uberjar...&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;b/uber&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:basis&lt;/span&gt;     &lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;basis
           &lt;span class=&quot;token symbol&quot;&gt;:class-dir&lt;/span&gt; class-dir
           &lt;span class=&quot;token symbol&quot;&gt;:uber-file&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;target/standalone.jar&quot;&lt;/span&gt;
           &lt;span class=&quot;token symbol&quot;&gt;:main&lt;/span&gt;      &#39;acme.main&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;To build the project, run &lt;code&gt;clj -T:build uber&lt;/code&gt;. This will create the uberjar &lt;code&gt;standalone.jar&lt;/code&gt; in the &lt;code&gt;target&lt;/code&gt; directory. The &lt;code&gt;uber&lt;/code&gt; in &lt;code&gt;clj -T:build uber&lt;/code&gt; refers to the &lt;code&gt;uber&lt;/code&gt; function from &lt;code&gt;build.clj&lt;/code&gt;. Since the build system is a Clojure program, you can customise it however you like. If we try to run the uberjar now, we’ll get an error:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# build the uberjar&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;clj &lt;span class=&quot;token parameter variable&quot;&gt;-T:build&lt;/span&gt; uber
&lt;span class=&quot;token comment&quot;&gt;# Cleaning build directory...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Copying files...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Compiling Clojure...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Building Uberjar...&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# run the uberjar&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;java&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-jar&lt;/span&gt; target/standalone.jar
&lt;span class=&quot;token comment&quot;&gt;# Error: Could not find or load main class acme.main&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Caused by: java.lang.ClassNotFoundException: acme.main&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This error occurred because the Main class that is required by Java isn’t built. To fix this, we need to add the &lt;code&gt;:gen-class&lt;/code&gt; directive in our main namespace. This will instruct Clojure to create the Main class from the &lt;code&gt;-main&lt;/code&gt; function.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/acme/main.clj&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-clojure&quot;&gt;&lt;code class=&quot;language-clojure&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ns&lt;/span&gt; acme.main
  &lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:gen-class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;;; ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you rebuild the project and run &lt;code&gt;java -jar target/standalone.jar&lt;/code&gt; again, it should work perfectly. Now that we have a working build script, we can write the Dockerfile:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;Dockerfile&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# install additional dependencies here in the base layer&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# separate base from build layer so any additional deps installed are cached&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; clojure:temurin-21-tools-deps-bookworm-slim &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; base&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; base &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; build&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;WORKDIR&lt;/span&gt; /opt&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; . .&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;RUN&lt;/span&gt; clj -T:build uber&lt;/span&gt;

&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;FROM&lt;/span&gt; eclipse-temurin:21-alpine &lt;span class=&quot;token keyword&quot;&gt;AS&lt;/span&gt; prod&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;COPY&lt;/span&gt; &lt;span class=&quot;token options&quot;&gt;&lt;span class=&quot;token property&quot;&gt;--from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;build&lt;/span&gt;&lt;/span&gt; /opt/target/standalone.jar /&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPOSE&lt;/span&gt; 8080&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;java&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;-jar&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;standalone.jar&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn8&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;8.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Here&#39;s a cool resource I found when researching Java Dockerfiles: &lt;a href=&quot;https://whichjdk.com&quot;&gt;WhichJDK&lt;/a&gt;. It provides a comprehensive comparison of the different JDKs available and recommendations on which one you should use. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;It’s a &lt;a href=&quot;https://www.docker.com/blog/multi-stage-builds/&quot;&gt;multi-stage Dockerfile&lt;/a&gt;. We use the official Clojure Docker image as the layer to build the uberjar. Once it’s built, we copy it to a smaller Docker image that only contains the Java runtime.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn8&quot; id=&quot;snref8&quot;&gt;[8]&lt;/a&gt;
        &lt;/sup&gt; By doing this, we get a smaller container image as well as a faster Docker build time because the layers are better cached.&lt;/p&gt;
&lt;p&gt;That should be all for packaging the app. We can move on to the deployment now.&lt;/p&gt;
&lt;h2 id=&quot;deploying-with-fly.io&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#deploying-with-fly.io&quot;&gt;Deploying with Fly.io&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First things first, you’ll need to &lt;a href=&quot;https://fly.io/docs/flyctl/install/&quot;&gt;install &lt;code&gt;flyctl&lt;/code&gt;&lt;/a&gt;, Fly’s CLI tool for interacting with their platform. Create &lt;a href=&quot;https://fly.io/app/sign-up&quot;&gt;a Fly.io account&lt;/a&gt; if you haven’t already. Then run &lt;code&gt;fly auth login&lt;/code&gt; to authenticate &lt;code&gt;flyctl&lt;/code&gt; with your account.&lt;/p&gt;
&lt;p&gt;Next, we’ll need to create a new &lt;a href=&quot;https://fly.io/docs/apps/overview/&quot;&gt;Fly App&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;fly app create
&lt;span class=&quot;token comment&quot;&gt;# ? Choose an app name (leave blank to generate one): &lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# automatically selected personal organization: Ryan Martin&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# New app created: blue-water-6489&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Another way to do this is with the &lt;code&gt;fly launch&lt;/code&gt; command, which automates a lot of the app configuration for you. We have some steps to do that are not done by &lt;code&gt;fly launch&lt;/code&gt;, so we’ll be configuring the app manually. I also already have a &lt;code&gt;fly.toml&lt;/code&gt; file ready that you can straight away copy to your project.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;fly.toml&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-toml&quot;&gt;&lt;code class=&quot;language-toml&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# replace these with your app and region name&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# run `fly platform regions` to get a list of regions&lt;/span&gt;
&lt;span class=&quot;token key property&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;blue-water-6489&#39;&lt;/span&gt; 
&lt;span class=&quot;token key property&quot;&gt;primary_region&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sin&#39;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;DB_DATABASE&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/data/database.db&quot;&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;http_service&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;internal_port&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;8080&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;force_https&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;auto_stop_machines&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;stop&quot;&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;auto_start_machines&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;min_machines_running&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;mounts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;source&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;data&quot;&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;destination&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/data&quot;&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;initial_sie&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token table class-name&quot;&gt;vm&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;size&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shared-cpu-1x&quot;&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;memory&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;512mb&quot;&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;cpus&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token key property&quot;&gt;cpu_kind&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;shared&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;These are mostly the default configuration values with some additions. Under the &lt;code&gt;[env]&lt;/code&gt; section, we’re setting the SQLite database location to &lt;code&gt;/data/database.db&lt;/code&gt;. The &lt;code&gt;database.db&lt;/code&gt; file itself will be stored in a persistent &lt;a href=&quot;https://fly.io/docs/volumes/overview/&quot;&gt;Fly Volume&lt;/a&gt; mounted on the &lt;code&gt;/data&lt;/code&gt; directory. This is specified under the &lt;code&gt;[mounts]&lt;/code&gt; section. Fly Volumes are similar to regular Docker volumes but are designed for Fly’s micro VMs.&lt;/p&gt;
&lt;p&gt;We’ll need to set the &lt;code&gt;AUTH_USER&lt;/code&gt; and &lt;code&gt;AUTH_PASSWORD&lt;/code&gt; environment variables too, but not through the &lt;code&gt;fly.toml&lt;/code&gt; file as these are sensitive values. To securely set these credentials with Fly, we can set them as &lt;a href=&quot;https://fly.io/docs/apps/secrets/&quot;&gt;app secrets&lt;/a&gt;. They’re stored encrypted and will be automatically injected into the app at boot time.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;fly secrets &lt;span class=&quot;token builtin class-name&quot;&gt;set&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;AUTH_USER&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;hi@ryanmartin.me &lt;span class=&quot;token assign-left variable&quot;&gt;AUTH_PASSWORD&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;not-so-secure-password
&lt;span class=&quot;token comment&quot;&gt;# Secrets are staged for the first deployment&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;With this, the configuration is done and we can deploy the app using &lt;code&gt;fly deploy&lt;/code&gt;:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;fly deploy
&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Checking DNS configuration for blue-water-6489.fly.dev&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# Visit your newly deployed app at https://blue-water-6489.fly.dev/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The first deployment will take longer since it’s building the Docker image for the first time. Subsequent deployments should be faster due to the cached image layers. You can click on the link to view the deployed app, or you can also run &lt;code&gt;fly open&lt;/code&gt;, which will do the same thing. Here’s the app in action:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/clojure-fly/-0hDAZFbRj-800.webp&quot; alt=&quot;The app in action&quot; width=&quot;800&quot; height=&quot;488&quot;&gt;&lt;/p&gt;
&lt;p&gt;If you made additional changes to the app or &lt;code&gt;fly.toml&lt;/code&gt;, you can redeploy the app using the same command, &lt;code&gt;fly deploy&lt;/code&gt;. The app is configured to auto stop/start, which helps to cut costs when there’s not a lot of traffic to the site. If you want to take down the deployment, you’ll need to delete the app itself using &lt;code&gt;fly app destroy &amp;lt;your app name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;adding-a-production-repl&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#adding-a-production-repl&quot;&gt;Adding a Production REPL&lt;/a&gt;&lt;/h2&gt;
&lt;aside id=&quot;sn9&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;9.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Another (non-technically important) argument for live/production REPLs is just because it&#39;s cool. Ever since I read the story about &lt;a href=&quot;https://news.ycombinator.com/item?id=31234338&quot;&gt;NASA&#39;s programmers debugging a spacecraft through a live REPL&lt;/a&gt;, I&#39;ve always wanted to try it at least once. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This is an interesting topic in the Clojure community, with varying opinions on whether or not it’s a good idea. Personally, I find having a REPL connected to the live app helpful, and I often use it for debugging and running queries on the live database.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn9&quot; id=&quot;fnref9&quot;&gt;[9]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn9&quot; id=&quot;snref9&quot;&gt;[9]&lt;/a&gt;
        &lt;/sup&gt; Since we’re using SQLite, we don’t have a database server we can directly connect to, unlike Postgres or MySQL.&lt;/p&gt;
&lt;p&gt;If you’re brave, you can even restart the app directly without redeploying from the REPL. You can easily go wrong with it, which is why some prefer not to use it.&lt;/p&gt;
&lt;p&gt;For this project, we’re gonna add a &lt;a href=&quot;https://clojure.org/reference/repl_and_main#_launching_a_socket_server&quot;&gt;socket REPL&lt;/a&gt;. It’s very simple to add (you just need to add a JVM option) and it doesn’t require additional dependencies like &lt;a href=&quot;https://nrepl.org/&quot;&gt;nREPL&lt;/a&gt;. Let’s update the Dockerfile:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;Dockerfile&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;EXPOSE&lt;/span&gt; 7888&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;java&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;-Dclojure.server.repl={:port 7888 :accept clojure.core.server/repl}&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;-jar&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;standalone.jar&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The socket REPL will be listening on port 7888. If we redeploy the app now, the REPL will be started, but we won’t be able to connect to it. That’s because we haven’t exposed the service through &lt;a href=&quot;https://fly.io/docs/reference/fly-proxy/&quot;&gt;Fly proxy&lt;/a&gt;. We can do this by adding the socket REPL as a service in the &lt;code&gt;[services]&lt;/code&gt; section in &lt;code&gt;fly.toml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;However, doing this will also expose the REPL port to the public. This means that anyone can connect to your REPL and possibly mess with your app. Instead, what we want to do is to configure the socket REPL as a private service.&lt;/p&gt;
&lt;p&gt;By default, all Fly apps in your organisation live in the same &lt;a href=&quot;https://fly.io/docs/networking/private-networking/&quot;&gt;private network&lt;/a&gt;. This private network, called 6PN, connects the apps in your organisation through &lt;a href=&quot;https://www.wireguard.com/&quot;&gt;WireGuard tunnels&lt;/a&gt; (a VPN) using IPv6. Fly private services aren’t exposed to the public internet but can be reached from this private network. We can then use Wireguard to connect to this private network to reach our socket REPL.&lt;/p&gt;
&lt;p&gt;Fly VMs are also configured with the hostname &lt;code&gt;fly-local-6pn&lt;/code&gt;, which maps to its 6PN address. This is analogous to &lt;code&gt;localhost&lt;/code&gt;, which points to your loopback address &lt;code&gt;127.0.0.1&lt;/code&gt;. To expose a service to 6PN, all we have to do is bind or serve it to &lt;code&gt;fly-local-6pn&lt;/code&gt; instead of the usual &lt;code&gt;0.0.0.0&lt;/code&gt;. We have to update the socket REPL options to:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;Dockerfile&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-dockerfile&quot;&gt;&lt;code class=&quot;language-dockerfile&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token instruction&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;ENTRYPOINT&lt;/span&gt; [&lt;span class=&quot;token string&quot;&gt;&quot;java&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;-Dclojure.server.repl={:port 7888,:address &#92;&quot;fly-local-6pn&#92;&quot;,:accept clojure.core.server/repl}&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;-jar&quot;&lt;/span&gt;, &lt;span class=&quot;token string&quot;&gt;&quot;standalone.jar&quot;&lt;/span&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn10&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;10.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;If you encounter errors related to WireGuard when running &lt;code&gt;fly proxy&lt;/code&gt;, you can run &lt;code&gt;fly doctor&lt;/code&gt;, which will hopefully detect issues with your local setup and also suggest fixes for them. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#snref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;After redeploying, we can use the &lt;code&gt;fly proxy&lt;/code&gt; command to forward the port from the remote server to our local machine.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fn10&quot; id=&quot;fnref10&quot;&gt;[10]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#sn10&quot; id=&quot;snref10&quot;&gt;[10]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;fly proxy &lt;span class=&quot;token number&quot;&gt;7888&lt;/span&gt;:7888
&lt;span class=&quot;token comment&quot;&gt;# Proxying local port 7888 to remote [blue-water-6489.internal]:7888&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;In another shell, run:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;rlwrap &lt;span class=&quot;token function&quot;&gt;nc&lt;/span&gt; localhost &lt;span class=&quot;token number&quot;&gt;7888&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;# user=&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now we have a REPL connected to the production app! &lt;code&gt;rlwrap&lt;/code&gt; is used for &lt;a href=&quot;https://en.wikipedia.org/wiki/GNU_Readline&quot;&gt;readline&lt;/a&gt; functionality, e.g. up/down arrow keys, vi bindings. Of course, you can also connect to it from your editor.&lt;/p&gt;
&lt;h2 id=&quot;deploy-with-github-actions&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#deploy-with-github-actions&quot;&gt;Deploy with GitHub Actions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you’re using &lt;a href=&quot;https://github.com&quot;&gt;GitHub&lt;/a&gt;, we can also set up automatic deployments on pushes/PRs with &lt;a href=&quot;https://github.com/features/actions&quot;&gt;GitHub Actions&lt;/a&gt;. All you need is to create the workflow file:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;.github/workflows/fly.yaml&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-yaml&quot;&gt;&lt;code class=&quot;language-yaml&quot;&gt;&lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Fly Deploy
&lt;span class=&quot;token key atrule&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; main
  &lt;span class=&quot;token key atrule&quot;&gt;workflow_dispatch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;

&lt;span class=&quot;token key atrule&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;token key atrule&quot;&gt;deploy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token key atrule&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; Deploy app
    &lt;span class=&quot;token key atrule&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; ubuntu&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;latest
    &lt;span class=&quot;token key atrule&quot;&gt;concurrency&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; deploy&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;group
    &lt;span class=&quot;token key atrule&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; actions/checkout@v4
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; superfly/flyctl&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;actions/setup&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;flyctl@master
      &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token key atrule&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flyctl deploy &lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;remote&lt;span class=&quot;token punctuation&quot;&gt;-&lt;/span&gt;only
        &lt;span class=&quot;token key atrule&quot;&gt;env&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
          &lt;span class=&quot;token key atrule&quot;&gt;FLY_API_TOKEN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; $&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; secrets.FLY_API_TOKEN &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;To get this to work, you’ll need to create a &lt;a href=&quot;https://fly.io/docs/security/tokens/&quot;&gt;deploy token&lt;/a&gt; from your app’s dashboard. Then, in your GitHub repo, create a new repository secret called &lt;code&gt;FLY_API_TOKEN&lt;/code&gt; with the value of your deploy token. Now, whenever you push to the &lt;code&gt;main&lt;/code&gt; branch, this workflow will automatically run and deploy your app. You can also manually run the workflow from GitHub because of the &lt;code&gt;workflow_dispatch&lt;/code&gt; option.&lt;/p&gt;
&lt;h2 id=&quot;end&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/clojure-fly/#end&quot;&gt;End&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As always, all the code is available &lt;a href=&quot;https://github.com/rmrt1n/rmrt1n.github.io/tree/main/code/clojure-fly&quot;&gt;on GitHub&lt;/a&gt;. Originally, this post was just about deploying to Fly.io, but along the way, I kept adding on more stuff until it essentially became my version of the &lt;a href=&quot;https://github.com/seancorfield/usermanager-example/&quot;&gt;user manager example app&lt;/a&gt;. Anyway, hope this post provided a good view into web development with Clojure. As a bonus, here are some additional resources on deploying Clojure apps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://bogoyavlensky.com/blog/deploying-full-stack-clojure-app-with-kamal/&quot;&gt;Deploying a Full-Stack Clojure App With Kamal on a Single Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ericnormand.me/article/jvm-deployment-options&quot;&gt;JVM Deployment Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tonitalksdev.com/deploying-clojure-like-a-seasoned-hobbyist&quot;&gt;Deploying Clojure Like a Seasoned Hobbyist&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.braveclojure.com/quests/deploy/&quot;&gt;Brave Clojure’s Deploy Quest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr class=&quot;footnotes-sep asterism&quot;&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The way Fly.io works under the hood is pretty clever. Instead of running the container image with a runtime like Docker, the image is unpacked and “loaded” into a VM. See &lt;a href=&quot;https://www.youtube.com/watch?v=7iypMRKniPU&quot;&gt;this video explanation&lt;/a&gt; for more details. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;If you’re interested in learning Clojure, my recommendation is to follow &lt;a href=&quot;https://clojure.org/guides/getting_started&quot;&gt;the official getting started guide&lt;/a&gt; and join the &lt;a href=&quot;https://clojurians.slack.com/&quot;&gt;Clojurians Slack&lt;/a&gt;. Also, read through this &lt;a href=&quot;https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f&quot;&gt;list of introductory resources&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Kit was a big influence on me when I first started learning web development in Clojure. I never used it directly, but I did use their library choices and project structure as a base for my own projects. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There’s no “Rails” for the Clojure ecosystem (yet?). The prevailing opinion is to build your own “framework” by composing different libraries together. Most of these libraries are stable and are already used in production by big companies, so don’t let this discourage you from doing web development in Clojure! &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;There might be some keys that you add or remove, but the structure of the config file stays the same. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;“assoc” (associate) is a Clojure slang that means to add or update a key-value pair in a map. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;For more details on how basic authentication works, check out &lt;a href=&quot;https://www.rfc-editor.org/rfc/rfc7617.html&quot;&gt;the specification&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Here’s a cool resource I found when researching Java Dockerfiles: &lt;a href=&quot;https://whichjdk.com&quot;&gt;WhichJDK&lt;/a&gt;. It provides a comprehensive comparison of the different JDKs available and recommendations on which one you should use. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn9&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Another (non-technically important) argument for live/production REPLs is just because it’s cool. Ever since I read the story about &lt;a href=&quot;https://news.ycombinator.com/item?id=31234338&quot;&gt;NASA’s programmers debugging a spacecraft through a live REPL&lt;/a&gt;, I’ve always wanted to try it at least once. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn10&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;If you encounter errors related to WireGuard when running &lt;code&gt;fly proxy&lt;/code&gt;, you can run &lt;code&gt;fly doctor&lt;/code&gt;, which will hopefully detect issues with your local setup and also suggest fixes for them. &lt;a href=&quot;https://ryanmartin.me/articles/clojure-fly/#fnref10&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>How to Implement Automatic CLI Login</title>
    <link href="https://ryanmartin.me/articles/cli-login/" />
    <updated>2026-04-04T12:25:25Z</updated>
    <id>https://ryanmartin.me/articles/cli-login/</id>
    <content type="html">
&lt;html&gt;&lt;head&gt;&lt;/head&gt;&lt;body&gt;&lt;p&gt;I’ve recently had the chance to work on an “automatic CLI login” feature for a &lt;a href=&quot;https://github.com/Argus-Labs/world-cli&quot;&gt;CLI tool&lt;/a&gt; at my day job. What I mean by automatic CLI login here is when a CLI tool automatically logs in a user, so it can perform actions on behalf of the user.&lt;/p&gt;
&lt;p&gt;It usually works like this. When you type a command like &lt;code&gt;cli login&lt;/code&gt; in your terminal, a login page will open in your web browser. After you log in, the page will show a message like “Your CLI is connected now”. When you go back to the terminal, it will say that it’s “connected” and you can now perform commands that require authentication.&lt;/p&gt;
&lt;p&gt;The connection here is often in the form of an access token or an API key. This allows the CLI to stay logged in and perform authenticated requests to a remote server.&lt;/p&gt;
&lt;p&gt;You’ve probably experienced this type of interaction before. Many modern CLI tools have some sort of command to allow you to log in from the command line. Examples include &lt;a href=&quot;https://supabase.com/docs/reference/cli/introduction&quot;&gt;Supabase CLI&lt;/a&gt;, &lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI&lt;/a&gt;, &lt;a href=&quot;https://graphite.dev/features/cli&quot;&gt;Graphite CLI&lt;/a&gt;, and Fly.io’s &lt;a href=&quot;https://fly.io/docs/flyctl/&quot;&gt;&lt;code&gt;flyctl&lt;/code&gt;&lt;/a&gt;. It’s a UX pattern that I like, as it hides a lot of complexity from the user.&lt;/p&gt;
&lt;aside id=&quot;sn1&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;1.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I learned about how this works mostly by digging into the &lt;a href=&quot;https://github.com/supabase/cli&quot;&gt;Supabase CLI code&lt;/a&gt;, so I&#39;m biased toward this approach. You can also see &lt;a href=&quot;https://supabase.com/blog/automatic-cli-login&quot;&gt;Supabase&#39;s blog post about this&lt;/a&gt; and &lt;a href=&quot;https://fly.io/laravel-bytes/making-the-cli-and-browser-talk/&quot;&gt;Fly&#39;s guide on implementing this in Laravel&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;There are probably many different ways to implement this, each with different trade-offs. In this post, I’ll write about just one way of doing this, which is also the same approach used by Supabase and Fly.io.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn1&quot; id=&quot;snref1&quot;&gt;[1]&lt;/a&gt;
        &lt;/sup&gt; It works for my particular use case and does not require refactoring my existing authentication logic.&lt;/p&gt;
&lt;aside class=&quot;toc&quot;&gt;&lt;div&gt;
      &lt;h2 id=&quot;toc&quot;&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#toc&quot; class=&quot;header-anchor&quot;&gt;Table of Contents&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#how-it-works&quot;&gt;How it Works&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#a-basic-authentication-system&quot;&gt;A Basic Authentication System&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#the-cli-login-endpoints&quot;&gt;The CLI Login Endpoints&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#writing-the-cli-tool&quot;&gt;Writing the CLI Tool&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#security-considerations&quot;&gt;Security Considerations&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#ecdh-key-exchange-%26-encryption&quot;&gt;ECDH Key Exchange &amp;amp; Encryption&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#securing-the-endpoints&quot;&gt;Securing the Endpoints&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;how-it-works&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#how-it-works&quot;&gt;How it Works&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At first glance, it looks like there is a two-way communication between the CLI tool and the browser. When you log in to the browser, you’ll receive an access token. Then the browser sends it to the CLI in some way?. Maybe the CLI starts up an HTTP server with an endpoint to receive the access token from the web page, like the diagram below?&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/cli-login/8zjhL3f3r0-2146.webp&quot; alt=&quot;Two-way communication scheme&quot; width=&quot;2146&quot; height=&quot;683&quot;&gt;&lt;/p&gt;
&lt;p&gt;This probably works, though I haven’t actually tested it yet. This isn’t the approach that I implemented, but it could be a good area for future experimentation.&lt;/p&gt;
&lt;aside id=&quot;sn2&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;2.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I call it one-way communication here, but what I meant is that only one participant, this being the CLI, is making the requests. Contrast this to the other approach, where both participants are sending requests to each other. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;How it works in Supabase CLI and &lt;code&gt;flyctl&lt;/code&gt; is that it uses a “one-way” communication scheme instead of a two-way one.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn2&quot; id=&quot;snref2&quot;&gt;[2]&lt;/a&gt;
        &lt;/sup&gt; Instead of the remote server sending the access token after the user has logged in, the CLI will keep polling for the access token in the background. At a high level, the flow should look like this diagram:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/cli-login/stdV4wS9zs-2146.webp&quot; alt=&quot;One-way communication scheme&quot; width=&quot;2146&quot; height=&quot;683&quot;&gt;&lt;/p&gt;
&lt;p&gt;The CLI will provide a session ID and ask the server to create a “CLI login session” when the user logs in successfully. An access token will be created for this specific session. Then, the CLI will request this access token from the server and store it for future requests.&lt;/p&gt;
&lt;p&gt;Since this approach uses &lt;a href=&quot;https://javascript.info/long-polling#regular-polling&quot;&gt;regular HTTP polling&lt;/a&gt;, it’s a potential source of &lt;a href=&quot;https://en.wikipedia.org/wiki/Denial-of-service_attack&quot;&gt;DDOS attacks&lt;/a&gt;. In a perfect world, a user needs to log in to the CLI only once after they install the tool. The server is unlikely to get too many login requests at a time. In the real world, attackers can keep sending login requests to the server to slow or take it down. We can mitigate this by rate-limiting the endpoints and blocking malicious IP addresses, but it’s not a perfect defence.&lt;/p&gt;
&lt;aside id=&quot;sn3&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;3.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;450,000 is the number of registered developers on &lt;a href=&quot;https://supabase.com/company&quot;&gt;their website&lt;/a&gt;. It&#39;s a vanity metric, but it&#39;s still an impressive number. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This is a trade-off between security and code simplicity. HTTP polling is much simpler to implement than other solutions like &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API&quot;&gt;WebSockets&lt;/a&gt; or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events&quot;&gt;SSE&lt;/a&gt;. This might not work for you depending on your &lt;a href=&quot;https://en.wikipedia.org/wiki/Threat_model&quot;&gt;threat model&lt;/a&gt;. But hey, if it works for Supabase, which has around 450,000 users,&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn3&quot; id=&quot;snref3&quot;&gt;[3]&lt;/a&gt;
        &lt;/sup&gt; it’ll probably work for me too.&lt;/p&gt;
&lt;p&gt;Now that we know how it works, let’s get into the code implementation. I’ll build a web server and a CLI tool for a fictional developer tools company, ACME Corp. The CLI tool will be called &lt;code&gt;acme&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;project-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ll use &lt;a href=&quot;https://kit.svelte.dev&quot;&gt;SvelteKit&lt;/a&gt; for this project, but the concepts apply to any web framework. I’ll try to avoid using any Svelte-specific features or terminologies so that you can apply this to any language or web framework you like.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# create a new sveltekit project &amp;amp; install dependencies&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; create svelte@latest acme
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;cd&lt;/span&gt; acme
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’ve heard good things about &lt;a href=&quot;https://orm.drizzle.team/&quot;&gt;Drizzle ORM&lt;/a&gt;, so I’ll also use it as the ORM for this project. The database will be &lt;a href=&quot;https://www.sqlite.org/&quot;&gt;SQLite&lt;/a&gt; using the &lt;a href=&quot;https://github.com/WiseLibs/better-sqlite3/&quot;&gt;&lt;code&gt;better-sqlite3&lt;/code&gt;&lt;/a&gt; library. We’ll need to install these too.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; drizzle-orm better-sqlite3
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;token parameter variable&quot;&gt;-D&lt;/span&gt; drizzle-kit&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Drizzle requires some setup steps before we can use it. First, we’ll create a Drizzle client:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/lib/db/db.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Database &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;better-sqlite3&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; drizzle &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm/better-sqlite3&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;sqlite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Database&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;data/database.db&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;db&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;drizzle&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sqlite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;And define the database schema:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/lib/db/schema.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sqliteTable&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; text &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm/sqlite-core&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;users&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqliteTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;users&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;user_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;primaryKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$defaultFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;email&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;email&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unique&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;password&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;password&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;sessions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqliteTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;sessions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;sessionId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;primaryKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$defaultFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;user_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;users&lt;/code&gt; table will store the user details, and the &lt;code&gt;sessions&lt;/code&gt; table will store the authentication sessions. The app will use traditional &lt;a href=&quot;https://roadmap.sh/guides/session-based-authentication&quot;&gt;session-based authentication&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then, we need to update the config file:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;drizzle.config.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; defineConfig &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-kit&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;defineConfig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;dialect&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;sqlite&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./src/lib/db/schema.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;dbCredentials&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;url&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./data/database.db&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;To apply the changes to the database, run:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# create database directory&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;mkdir&lt;/span&gt; data

&lt;span class=&quot;token comment&quot;&gt;# apply changes to database&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token function&quot;&gt;pnpm&lt;/span&gt; drizzle-kit push&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This will create the &lt;code&gt;data/database.db&lt;/code&gt; SQLite database with our schema. You should only use &lt;code&gt;drizzle-kit push&lt;/code&gt; for prototyping or local development. If you need database migrations, use &lt;code&gt;drizzle-kit generate&lt;/code&gt; and &lt;code&gt;drizzle-kit migrate&lt;/code&gt;. See the &lt;a href=&quot;https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push&quot;&gt;official documentation&lt;/a&gt; for more info.&lt;/p&gt;
&lt;p&gt;Finally, start the development server by running &lt;code&gt;pnpm dev&lt;/code&gt;. The server should now be running at &lt;code&gt;http://localhost:5173&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id=&quot;a-basic-authentication-system&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#a-basic-authentication-system&quot;&gt;A Basic Authentication System&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As the focus of this post is on automatic CLI login, we won’t be doing anything fancy with user authentication. It’ll use a database-backed session-based authentication. I’ll also skip hashing the password and just store it in plaintext. You should never store passwords like this in a production system. Either use a third-party authentication service or follow the &lt;a href=&quot;https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html&quot;&gt;OWASP guide to password storage&lt;/a&gt;.&lt;/p&gt;
&lt;aside id=&quot;sn4&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;4.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I made this choice just to keep the code as small as possible. You shouldn&#39;t do this in a real app unless you have a really specific reason. This UX is unfamiliar and can confuse users. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;First, let’s build a login page. For this app, the login page will serve as both the login and the signup page.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn4&quot; id=&quot;snref4&quot;&gt;[4]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/login/+page.svelte&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;log in&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;h1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;form&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;method&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;POST&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;email&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;john.doe@email.com&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;label&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;for&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;password&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;label&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;input&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;log in&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;form&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The login form sends a POST request with the user’s email and password to this handler:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/login/+page.server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; db &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/db&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sessions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; users &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/schema&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; fail&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; redirect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@sveltejs/kit&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; eq &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).Actions} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;actions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function-variable function&quot;&gt;default&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; form &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; request&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;formData&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; email &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;email&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; form&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;password&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;email &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;missing email or password&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; userData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;next&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// register user if not exists &amp;amp; create session&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; newUserData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; email&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; password &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;returning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; newUserData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;returning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;303&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// else login&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;email &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;email &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; password &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;password&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;returning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;303&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; next&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;invalid email or password&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The exported &lt;code&gt;actions&lt;/code&gt; map is &lt;a href=&quot;https://kit.svelte.dev/docs/form-actions&quot;&gt;SvelteKit’s version of form action handlers&lt;/a&gt;. You can just focus on the function in the &lt;code&gt;default&lt;/code&gt; action.&lt;/p&gt;
&lt;p&gt;If the user record doesn’t exist in the database, this function will create a new user and session in the database, set the required cookie in the response header, and redirect the user to the value in the &lt;code&gt;next&lt;/code&gt; search param. If &lt;code&gt;next&lt;/code&gt; is not specified, it will redirect to the home page instead. If the user is already registered, it will directly create the session, set the cookie, and redirect the user.&lt;/p&gt;
&lt;p&gt;We’ll also add route protection to the home page. Unauthenticated requests to the home page will be redirected to the login page.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/+page.server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; db &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/db&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sessions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; users &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/schema&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; redirect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@sveltejs/kit&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; eq &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).PageServerLoad} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookies &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sessionCookie &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sessionCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sessionData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessionCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sessionData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;load&lt;/code&gt; function is a &lt;a href=&quot;https://kit.svelte.dev/docs/load&quot;&gt;function that runs before the page is rendered&lt;/a&gt;. It’s similar to page/route controllers in other web frameworks like &lt;a href=&quot;https://www.djangoproject.com/&quot;&gt;Django&lt;/a&gt; and &lt;a href=&quot;https://rubyonrails.org/&quot;&gt;Ruby on Rails&lt;/a&gt;. If you open &lt;code&gt;http://localhost:5173&lt;/code&gt; in your browser, you should be redirected to the login page.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/cli-login/tAQxkDcEjc-800.webp&quot; alt=&quot;Login page&quot; width=&quot;800&quot; height=&quot;485&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;the-cli-login-endpoints&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#the-cli-login-endpoints&quot;&gt;The CLI Login Endpoints&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now let’s get to the main focus of this post. We’ll need to add new tables in our database schema to support this feature:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/lib/db/schema.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;accessTokens&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqliteTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;access_tokens&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;token_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;primaryKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$defaultFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;user_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;cliSessions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqliteTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cli_sessions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cliSessionId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cli_session_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;primaryKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$defaultFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;token_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; accessTokens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;access_tokens&lt;/code&gt; table will store the users’ access tokens, while the &lt;code&gt;cli_sessions&lt;/code&gt; table will store the “login sessions” triggered by the CLI tool. When a user runs the command &lt;code&gt;acme login&lt;/code&gt;, it will trigger a login session and an access token to be created on the server.&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;pnpm drizzle-kit push&lt;/code&gt; again to apply the changes to the database. If you encounter a SQLite read-only error in SvelteKit, just restart the dev server.&lt;/p&gt;
&lt;p&gt;We’ll also need to add new endpoints, &lt;code&gt;/cli/login&lt;/code&gt; and &lt;code&gt;cli/login/:session&lt;/code&gt;. Before we implement these endpoints, we’ll need a page to show the “Your CLI is now connected” message once the user has logged in.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/cli/login/+page.svelte&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;Your CLI is connected now. You can close this tab.&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;p&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now, here’s the code for the &lt;code&gt;/cli/login&lt;/code&gt; route handler:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/cli/login/+page.server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; db &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/db&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; accessTokens&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cliSessions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; users &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/schema&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; redirect &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@sveltejs/kit&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; eq &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).PageServerLoad} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sessionCookie &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; next &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;encodeURIComponent&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pathname &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;search&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;sessionCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/login?next=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;next&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sessionData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessionCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; sessionData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// if there is no valid session, redirect to login&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;302&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/login?next=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;next&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// get user from session id&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; userData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;leftJoin&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;users&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;sessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; sessionCookie&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; userData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;users

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSessionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;missing search params&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSessionData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cliSessionData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// create cli session &amp;amp; access token if it doesn&#39;t exist&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;cliSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessTokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;returning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This function first checks if the user is logged in using the cookie. The mechanism is almost the same as on the home page, with the only difference being that we’re setting the &lt;code&gt;next&lt;/code&gt; search parameter to point back to this URL. This will redirect the user back to &lt;code&gt;/cli/login&lt;/code&gt; after they have logged in.&lt;/p&gt;
&lt;p&gt;If the user is already logged in,  a CLI session and an access token will be inserted into the database. Then, it’ll render the success message page from before. Note, in SvelteKit, after the &lt;code&gt;load&lt;/code&gt; function in &lt;code&gt;+page.server.js&lt;/code&gt; is called, the page in &lt;code&gt;+page.svelte&lt;/code&gt; will be rendered. That’s why we don’t have to tell this function to render the page.&lt;/p&gt;
&lt;p&gt;Next is the &lt;code&gt;/cli/login/:session&lt;/code&gt; route handler:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/cli/login/[session]/+server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; db &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/db&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cliSessions &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/db/schema&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; error &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;@sveltejs/kit&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; eq &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;drizzle-orm&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).RequestHandler} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; params &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; session &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; params

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSessionData &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;select&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;where&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eq&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; session&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSession &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; cliSessionData&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;cliSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; cliSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This function checks if the session in the URL’s path parameter is a valid CLI session in the database. If it is, it’ll return the access token in the response body. This token will then be stored by the CLI tool and used in subsequent requests.&lt;/p&gt;
&lt;h2 id=&quot;writing-the-cli-tool&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#writing-the-cli-tool&quot;&gt;Writing the CLI Tool&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our &lt;code&gt;acme&lt;/code&gt; CLI tool will just be a single Node.js script. It’ll have only one command, &lt;code&gt;acme login&lt;/code&gt;. As mentioned in the previous section, the CLI will request a login session and poll for the access token:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;acme.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; crypto &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;crypto&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; exec &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;child_process&#39;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _acme&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; argv1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv

&lt;span class=&quot;token comment&quot;&gt;// exit if program isn&#39;t run with the login command&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;argv1 &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; argv1 &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;usage: node acme.js login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;host&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;http://localhost:5173&#39;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;session&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// open the URL in the default browser&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;url&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;host&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/cli/login?session=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;session&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;xdg-open &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Here&#39;s your login link:&#92;n&#92;n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// poll for the access token&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;interval&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;host&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/cli/login/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;session&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

      console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

      &lt;span class=&quot;token function&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;acme&lt;/code&gt; CLI will generate a random UUID for the session ID and include it as a search parameter in a URL to &lt;code&gt;/cli/login&lt;/code&gt;. Then, it’ll call &lt;code&gt;exec&lt;/code&gt;, which will run an OS process. In my case, it uses &lt;code&gt;xdg-open&lt;/code&gt;, a Linux program that opens a file or URL with the user’s default application. If you’re using a Mac, replace this with &lt;code&gt;open&lt;/code&gt;, and if you’re on Windows, replace it with &lt;code&gt;start&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you’re not already logged in, you will be redirected to the login page. Once you’ve logged in and seen the success message, the CLI should have already received the access token from the &lt;code&gt;/cli/login/:session&lt;/code&gt; endpoint. If you run this script, you should get something like this:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/cli-login/zkfVaed1b4-800.webp&quot; alt=&quot;The CLI login flow&quot; width=&quot;800&quot; height=&quot;485&quot;&gt;&lt;/p&gt;
&lt;p&gt;Nice! We now have a working automatic CLI login system. Note, I cleared the cookies in the video above so that I can log in again. If you didn’t do this, you’ll go straight to the success message as you’re already logged in. I also created a shell alias to make the script look like a “proper” CLI tool:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# create the alias&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token assign-left variable&quot;&gt;acme&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node acme.js&#39;&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# then you can run&lt;/span&gt;
&lt;code style=&quot;user-select:none;color:var(--color-links)&quot;&gt;$ &lt;/code&gt;acme login&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2 id=&quot;security-considerations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#security-considerations&quot;&gt;Security Considerations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ve mentioned DDOS attacks before as one of the vulnerabilities of this login flow. I’ll mention a few more and also explain the ways to mitigate them. This isn’t a comprehensive list of security vulnerabilities though. You might need to conduct security tests or assessments depending on your threat model.&lt;/p&gt;
&lt;p&gt;One type of attack we’re vulnerable to is the &lt;a href=&quot;https://en.wikipedia.org/wiki/Man-in-the-middle_attack&quot;&gt;man-in-the-middle attack&lt;/a&gt;. A man-in-the-middle attack can happen when we try to log in over an insecure connection. An attacker can listen to your network traffic and steal your access tokens. Thankfully, this kind of attack is easy to mitigate by using a secure connection like HTTPS. HTTPS encrypts your network traffic, preventing a third party from reading your network activity.&lt;/p&gt;
&lt;p&gt;Another kind of attack is a &lt;a href=&quot;https://en.wikipedia.org/wiki/Brute-force_attack&quot;&gt;brute force attack&lt;/a&gt;. Since the endpoints to poll for the access tokens don’t require authentication, attackers can brute force UUID values until they get an access token. This can be mitigated by expiring the CLI sessions and applying the same strategies as with DDOS attacks.&lt;/p&gt;
&lt;p&gt;Both of these attacks have one thing in common. If an attacker gets a hold of your access token, you’re hacked. We can increase the security of our app by encrypting the access tokens. This way, even if attackers obtain an encrypted access token, they won’t be able to decrypt it.&lt;/p&gt;
&lt;p&gt;Encrypting the tokens presents some challenges. How do we ensure that only the CLI can decrypt the token? What encryption scheme should be used? What if an attacker obtained the decryption key?&lt;/p&gt;
&lt;h2 id=&quot;ecdh-key-exchange-%26-encryption&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#ecdh-key-exchange-%26-encryption&quot;&gt;ECDH Key Exchange &amp;amp; Encryption&lt;/a&gt;&lt;/h2&gt;
&lt;aside id=&quot;sn5&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;5.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Check out &lt;a href=&quot;https://cryptobook.nakov.com/asymmetric-key-ciphers/ecdh-key-exchange&quot;&gt;the section on ECDH&lt;/a&gt; from the &lt;a href=&quot;https://cryptobook.nakov.com/&quot;&gt;Practical Cryptography for Developers&lt;/a&gt; book for more details on how the algorithm works. It&#39;s also a great resource for learning about cryptography in general without going too deep into the maths. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;One possible solution is to use &lt;a href=&quot;https://en.wikipedia.org/wiki/Elliptic-curve_Diffie%E2%80%93Hellman&quot;&gt;ECDH key exchange&lt;/a&gt;. ECDH (Elliptic Curve Diffie–Hellman Key Exchange) is a method that allows two parties to establish a shared secret over an insecure channel. ECDH is based on &lt;a href=&quot;https://en.wikipedia.org/wiki/Elliptic-curve_cryptography&quot;&gt;ECC&lt;/a&gt; (elliptic curve cryptography), a modern family of public-key cryptography and is considered the modern successor of RSA.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn5&quot; id=&quot;snref5&quot;&gt;[5]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;aside id=&quot;sn6&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;6.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;This post assumes you know about RSA and basic cryptography concepts. It&#39;s fine if you don&#39;t, but the concepts explained will make more sense if you do. Here&#39;s a good &lt;a href=&quot;https://www.comparitech.com/blog/information-security/cryptography-guide/&quot;&gt;introduction to cryptography article&lt;/a&gt; that explains the basic concepts, as well as resources if you want to learn further. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Here’s a high-level explanation of how ECDH works:&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn6&quot; id=&quot;snref6&quot;&gt;[6]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alice generates a random ECC key pair: &lt;strong&gt;alicePriv&lt;/strong&gt; &amp;amp; &lt;strong&gt;alicePub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bob generates a random ECC key pair: &lt;strong&gt;bobPriv&lt;/strong&gt; &amp;amp; &lt;strong&gt;bobPub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Alice and Bob exchange their public keys over an insecure channel like the Internet.&lt;/li&gt;
&lt;li&gt;Alice calculates the &lt;strong&gt;secret&lt;/strong&gt; = &lt;strong&gt;alicePriv&lt;/strong&gt; * &lt;strong&gt;bobPub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bob calculates the &lt;strong&gt;secret&lt;/strong&gt; = &lt;strong&gt;bobPriv&lt;/strong&gt; * &lt;strong&gt;alicePub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Alice and Bob now have the same shared &lt;strong&gt;secret&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This works because, in the ECDH algorithm, &lt;strong&gt;alicePriv&lt;/strong&gt; * &lt;strong&gt;bobPub&lt;/strong&gt; is equal to &lt;strong&gt;bobPriv&lt;/strong&gt; * &lt;strong&gt;alicePub&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unlike RSA, ECC doesn’t directly provide a way to do asymmetric encryption. Instead, we have to devise our own hybrid encryption scheme using a combination of ECDH and symmetric encryption. We can use any symmetric encryption algorithms we want like &lt;a href=&quot;https://cryptobook.nakov.com/symmetric-key-ciphers/aes-cipher-concepts&quot;&gt;AES&lt;/a&gt; or &lt;a href=&quot;https://en.wikipedia.org/wiki/ChaCha20-Poly1305&quot;&gt;ChaCha20-Poly1305&lt;/a&gt;. Here’s how the hybrid encryption scheme works:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Alice generates a random ECC key pair: &lt;strong&gt;alicePriv&lt;/strong&gt; &amp;amp; &lt;strong&gt;alicePub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bob generates a random ECC key pair: &lt;strong&gt;bobPriv&lt;/strong&gt; &amp;amp; &lt;strong&gt;bobPub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Alice and Bob exchange their public keys over the internet.&lt;/li&gt;
&lt;li&gt;Alice calculates the &lt;strong&gt;secret&lt;/strong&gt; = &lt;strong&gt;alicePriv&lt;/strong&gt; * &lt;strong&gt;bobPub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Alice encrypts a &lt;strong&gt;plaintext&lt;/strong&gt; using the &lt;strong&gt;secret&lt;/strong&gt; as the symmetric encryption key.&lt;/li&gt;
&lt;li&gt;Alice sends the &lt;strong&gt;ciphertext&lt;/strong&gt; to Bob over the Internet.&lt;/li&gt;
&lt;li&gt;Bob calculates the &lt;strong&gt;secret&lt;/strong&gt; = &lt;strong&gt;bobPriv&lt;/strong&gt; * &lt;strong&gt;alicePub&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bob decrypts the &lt;strong&gt;ciphertext&lt;/strong&gt; using the &lt;strong&gt;secret&lt;/strong&gt; as the decryption key.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While the underlying maths might be complex, the encryption scheme is straightforward. Now, let’s implement this in our code. We’ll use the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API&quot;&gt;WebCrypto API&lt;/a&gt; as the cryptography library.&lt;/p&gt;
&lt;h2 id=&quot;securing-the-endpoints&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#securing-the-endpoints&quot;&gt;Securing the Endpoints&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First, we need to update our &lt;code&gt;cli_sessions&lt;/code&gt; table definition to store the CLI’s public key.&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/lib/db/schema.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;cliSessions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sqliteTable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cli_sessions&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cliSessionId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;cli_session_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;primaryKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;$defaultFn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;token_id&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;references&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; accessTokens&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;pubKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;text&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pub_key&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;notNull&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// &amp;lt;-- new column&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;pnpm drizzle-kit push&lt;/code&gt; again. This time, you should be prompted to delete some records. As we’re just testing, we can always wipe the DB clean to start over.&lt;/p&gt;
&lt;p&gt;Next, we’ll update the &lt;code&gt;/cli/login&lt;/code&gt; endpoint to get the public key from the search parameter and insert it into the database:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/cli/login/+page.server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).PageServerLoad} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cookies&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; url &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;searchParams
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; cliSessionId &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;session&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; pubKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; params&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pub_key&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;cliSessionId &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;pubKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;missing search params&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; token &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;accessTokens&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; user&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;userId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;returning&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; db&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;insert&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSessions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; cliSessionId&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pubKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;tokenId&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;As for the &lt;code&gt;/cli/login/:session&lt;/code&gt; endpoint, instead of returning the access token directly, we’ll need to encrypt it first. We’ll refactor this endpoint’s handler function to implement the encryption:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/routes/cli/login/[session]/+server.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; bytesToHex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hexToBytes &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;$lib/utils&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;/** @type {import(&#39;./$types&#39;).RequestHandler} */&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;GET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; params &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;cliSession&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;404&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// generate ECC key pair&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; publicKey &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;namedCurve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;P-256&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;deriveKey&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; publicKeyBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exportKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;raw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; publicKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; remotePublicKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;importKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;raw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;hexToBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pubKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;namedCurve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;P-256&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// encrypt the access token using aes-gcm-256 and the secret key&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; enc &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextEncoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; secret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deriveKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; remotePublicKey &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AES-GCM&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;encrypt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;decrypt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// iv in aes-gcm-256 must be 12 bytes long&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; iv &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getRandomValues&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;12&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; encrypted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AES-GCM&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;iv&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; iv &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    secret&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    enc&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;cliSession&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tokenId&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bytesToHex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;encrypted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;pubKey&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bytesToHex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;publicKeyBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;iv&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;bytesToHex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn7&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;7.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;If I&#39;m using a third-party library like &lt;a href=&quot;https://github.com/paulmillr/noble-curves&quot;&gt;@noble/curves&lt;/a&gt;, I&#39;ll use &lt;a href=&quot;https://en.wikipedia.org/wiki/Curve25519&quot;&gt;X25519&lt;/a&gt; for ECDH. I&#39;m not an expert in ECC, and most of my algorithm/curve choices are based on &lt;a href=&quot;https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/&quot;&gt;this blog post on choosing an elliptic curve signature algorithm&lt;/a&gt;. Although it&#39;s about digital signatures (&lt;a href=&quot;https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages&quot;&gt;ECDSA&lt;/a&gt;), I still find it helpful for ECDH. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#snref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Now, this function will generate a random ECC key pair to calculate the secret using the public key provided by the CLI. I’m using the &lt;a href=&quot;https://csrc.nist.gov/csrc/media/events/workshop-on-elliptic-curve-cryptography-standards/documents/papers/session6-adalier-mehmet.pdf&quot;&gt;NIST P-256&lt;/a&gt; curve as the basis of the ECC key pair, as the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/EcKeyGenParams&quot;&gt;WebCrypto API only supports NIST curves&lt;/a&gt; at the moment.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#sn7&quot; id=&quot;snref7&quot;&gt;[7]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;This secret is then used as the key for the symmetric encryption algorithm. I use AES-GCM here because it’s also directly supported in WebCrypto. The secret here is 32 bytes long, which means we’ll use a 256-bit key. AES-GCM encryption with a 256-bit key requires a nonce value (&lt;code&gt;iv&lt;/code&gt;) of 12 bytes, which we also need to send to the user along with the server’s public key.&lt;/p&gt;
&lt;p&gt;The private and public keys are byte arrays here, so we also need some helper functions to decode byte arrays from/to hex strings:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/lib/utils.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;bytesToHex&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;bytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;...&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Uint8Array&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;bytes&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;padStart&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;0&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;hexToBytes&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;hex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; hexBytes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; hex&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;match&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex&quot;&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-source language-regex&quot;&gt;.{1,2}&lt;/span&gt;&lt;span class=&quot;token regex-delimiter&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;token regex-flags&quot;&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;??&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; Uint8Array&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;hexBytes&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;parseInt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;byte&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Finally, we have to update the CLI code to decrypt the access token:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;acme.js&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; bytesToHex&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; hexToBytes &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./src/lib/utils.js&#39;&lt;/span&gt;
&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;randomKeyPair&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;generateKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;namedCurve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;P-256&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;deriveKey&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;decrypt&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; publicKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; iv&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; encrypted&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; remotePublicKey &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;importKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token string&quot;&gt;&#39;raw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    publicKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;namedCurve&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;P-256&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; secret &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deriveKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ECDH&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;public&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; remotePublicKey &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AES-GCM&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;256&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;encrypt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;decrypt&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; decrypted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;AES-GCM&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;iv&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; iv &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; secret&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; encrypted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; dec &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TextDecoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; dec&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;decode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;decrypted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;login&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; host &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;http://localhost:5173&#39;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; session &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;randomUUID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; publicKey &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;randomKeyPair&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; publicKeyBuffer &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; crypto&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;subtle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exportKey&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;raw&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; publicKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;host&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/cli/login?session=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;session&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&amp;amp;pub_key=&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;bytesToHex&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;publicKeyBuffer&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;xdg-open &#39;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Here&#39;s your login link:&#92;n&#92;n&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;url&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#92;n&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; interval &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;host&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;/cli/login/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;session&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ok &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; pubKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; iv &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; data

        &lt;span class=&quot;token function&quot;&gt;decrypt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;privateKey&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hexToBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pubKey&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hexToBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;iv&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;hexToBytes&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;accessToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Your access token:&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; accessToken&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

        &lt;span class=&quot;token function&quot;&gt;clearInterval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;interval&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;_node&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _acme&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; argv1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;argv1 &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; argv1 &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;usage: node acme.js login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;login&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;decrypt&lt;/code&gt; function follows the same steps as the encryption process, just reversed. And that’s it. When we run the script now, we should see that the login flow still works the same:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/cli-login/V6YTFcZrKa-800.webp&quot; alt=&quot;Checking the new CLI login flow&quot; width=&quot;800&quot; height=&quot;485&quot;&gt;&lt;/p&gt;
&lt;p&gt;You can also see that the decrypted token is the same as the token in the database.&lt;/p&gt;
&lt;h2 id=&quot;what%E2%80%99s-next%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/cli-login/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can find the code for this project &lt;a href=&quot;https://github.com/rmrt1n/rmrt1n.github.io/tree/main/code/cli-login&quot;&gt;on GitHub&lt;/a&gt;. I’ve only covered the minimum required to implement automatic CLI login with encryption. There’s always room for improvement. E.g., you can implement additional features such as CLI session expiry, rate-limiting, token expiry, and more. Good luck!&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep asterism&quot;&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I learned about how this works mostly by digging into the &lt;a href=&quot;https://github.com/supabase/cli&quot;&gt;Supabase CLI code&lt;/a&gt;, so I’m biased toward this approach. You can also see &lt;a href=&quot;https://supabase.com/blog/automatic-cli-login&quot;&gt;Supabase’s blog post about this&lt;/a&gt; and &lt;a href=&quot;https://fly.io/laravel-bytes/making-the-cli-and-browser-talk/&quot;&gt;Fly’s guide on implementing this in Laravel&lt;/a&gt;. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I call it one-way communication here, but what I meant is that only one participant, this being the CLI, is making the requests. Contrast this to the other approach, where both participants are sending requests to each other. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;450,000 is the number of registered developers on &lt;a href=&quot;https://supabase.com/company&quot;&gt;their website&lt;/a&gt;. It’s a vanity metric, but it’s still an impressive number. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I made this choice just to keep the code as small as possible. You shouldn’t do this in a real app unless you have a really specific reason. This UX is unfamiliar and can confuse users. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Check out &lt;a href=&quot;https://cryptobook.nakov.com/asymmetric-key-ciphers/ecdh-key-exchange&quot;&gt;the section on ECDH&lt;/a&gt; from the &lt;a href=&quot;https://cryptobook.nakov.com/&quot;&gt;Practical Cryptography for Developers&lt;/a&gt; book for more details on how the algorithm works. It’s also a great resource for learning about cryptography in general without going too deep into the maths. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;This post assumes you know about RSA and basic cryptography concepts. It’s fine if you don’t, but the concepts explained will make more sense if you do. Here’s a good &lt;a href=&quot;https://www.comparitech.com/blog/information-security/cryptography-guide/&quot;&gt;introduction to cryptography article&lt;/a&gt; that explains the basic concepts, as well as resources if you want to learn further. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;If I’m using a third-party library like &lt;a href=&quot;https://github.com/paulmillr/noble-curves&quot;&gt;@noble/curves&lt;/a&gt;, I’ll use &lt;a href=&quot;https://en.wikipedia.org/wiki/Curve25519&quot;&gt;X25519&lt;/a&gt; for ECDH. I’m not an expert in ECC, and most of my algorithm/curve choices are based on &lt;a href=&quot;https://soatok.blog/2022/05/19/guidance-for-choosing-an-elliptic-curve-signature-algorithm-in-2022/&quot;&gt;this blog post on choosing an elliptic curve signature algorithm&lt;/a&gt;. Although it’s about digital signatures (&lt;a href=&quot;https://cryptobook.nakov.com/digital-signatures/ecdsa-sign-verify-messages&quot;&gt;ECDSA&lt;/a&gt;), I still find it helpful for ECDH. &lt;a href=&quot;https://ryanmartin.me/articles/cli-login/#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;/body&gt;&lt;/html&gt;
</content>
  </entry>
  <entry>
    <title>Advent of Code 2024 in Zig</title>
    <link href="https://ryanmartin.me/articles/aoc2024/" />
    <updated>2026-04-04T12:25:25Z</updated>
    <id>https://ryanmartin.me/articles/aoc2024/</id>
    <content type="html">
&lt;html&gt;&lt;head&gt;&lt;style id=&quot;MJX-SVG-styles&quot;&gt;
mjx-container[jax=&quot;SVG&quot;] {
  direction: ltr;
}

mjx-container[jax=&quot;SVG&quot;] &gt; svg {
  overflow: visible;
  min-height: 1px;
  min-width: 1px;
}

mjx-container[jax=&quot;SVG&quot;] &gt; svg a {
  fill: blue;
  stroke: blue;
}

mjx-assistive-mml {
  position: absolute !important;
  top: 0px;
  left: 0px;
  clip: rect(1px, 1px, 1px, 1px);
  padding: 1px 0px 0px 0px !important;
  border: 0px !important;
  display: block !important;
  width: auto !important;
  overflow: hidden !important;
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -khtml-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

mjx-assistive-mml[display=&quot;block&quot;] {
  width: 100% !important;
}

mjx-container[jax=&quot;SVG&quot;][display=&quot;true&quot;] {
  display: block;
  text-align: center;
  margin: 1em 0;
}

mjx-container[jax=&quot;SVG&quot;][display=&quot;true&quot;][width=&quot;full&quot;] {
  display: flex;
}

mjx-container[jax=&quot;SVG&quot;][justify=&quot;left&quot;] {
  text-align: left;
}

mjx-container[jax=&quot;SVG&quot;][justify=&quot;right&quot;] {
  text-align: right;
}

g[data-mml-node=&quot;merror&quot;] &gt; g {
  fill: red;
  stroke: red;
}

g[data-mml-node=&quot;merror&quot;] &gt; rect[data-background] {
  fill: yellow;
  stroke: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; line[data-line], svg[data-table] &gt; g &gt; line[data-line] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; rect[data-frame], svg[data-table] &gt; g &gt; rect[data-frame] {
  stroke-width: 70px;
  fill: none;
}

g[data-mml-node=&quot;mtable&quot;] &gt; .mjx-dashed, svg[data-table] &gt; g &gt; .mjx-dashed {
  stroke-dasharray: 140;
}

g[data-mml-node=&quot;mtable&quot;] &gt; .mjx-dotted, svg[data-table] &gt; g &gt; .mjx-dotted {
  stroke-linecap: round;
  stroke-dasharray: 0,140;
}

g[data-mml-node=&quot;mtable&quot;] &gt; g &gt; svg {
  overflow: visible;
}

[jax=&quot;SVG&quot;] mjx-tool {
  display: inline-block;
  position: relative;
  width: 0;
  height: 0;
}

[jax=&quot;SVG&quot;] mjx-tool &gt; mjx-tip {
  position: absolute;
  top: 0;
  left: 0;
}

mjx-tool &gt; mjx-tip {
  display: inline-block;
  padding: .2em;
  border: 1px solid #888;
  font-size: 70%;
  background-color: #F8F8F8;
  color: black;
  box-shadow: 2px 2px 5px #AAAAAA;
}

g[data-mml-node=&quot;maction&quot;][data-toggle] {
  cursor: pointer;
}

mjx-status {
  display: block;
  position: fixed;
  left: 1em;
  bottom: 1em;
  min-width: 25%;
  padding: .2em .4em;
  border: 1px solid #888;
  font-size: 90%;
  background-color: #F8F8F8;
  color: black;
}

foreignObject[data-mjx-xml] {
  font-family: initial;
  line-height: normal;
  overflow: visible;
}

mjx-container[jax=&quot;SVG&quot;] path[data-c], mjx-container[jax=&quot;SVG&quot;] use[data-c] {
  stroke-width: 3;
}
&lt;/style&gt;&lt;/head&gt;&lt;body&gt;&lt;aside id=&quot;sn1&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;1.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;I failed my first attempt horribly with &lt;a href=&quot;https://clojure.org/&quot;&gt;Clojure&lt;/a&gt; during Advent of Code 2023. Once I reached the later half of the event, I just couldn&#39;t solve the problems with a purely functional style. I could&#39;ve pushed through using imperative code, but I stubbornly chose not to and gave up... &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This post is about &lt;s&gt;six&lt;/s&gt; seven months late, but here are my takeaways from &lt;a href=&quot;https://adventofcode.com/&quot;&gt;Advent of Code 2024&lt;/a&gt;. It was my second time participating, and this time I actually managed to complete it.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn1&quot; id=&quot;fnref1&quot;&gt;[1]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn1&quot; id=&quot;snref1&quot;&gt;[1]&lt;/a&gt;
        &lt;/sup&gt; My goal was to learn a new language, &lt;a href=&quot;https://ziglang.org/&quot;&gt;Zig&lt;/a&gt;, and to improve my DSA and problem-solving skills.&lt;/p&gt;
&lt;p&gt;If you’re not familiar, Advent of Code is an annual programming challenge that runs every December. A new puzzle is released each day from December 1st to the 25th. There’s also a global leaderboard where people (and AI) race to get the fastest solves, but I personally don’t compete in it, mostly because I want to do it at my own pace.&lt;/p&gt;
&lt;p&gt;I went with Zig because I have been curious about it for a while, mainly because of its promise of &lt;a href=&quot;https://www.youtube.com/watch?v=Gv2I7qTux7g&quot;&gt;being a better C&lt;/a&gt; and because &lt;a href=&quot;https://tigerbeetle.com/&quot;&gt;TigerBeetle&lt;/a&gt; (one of the coolest databases now) is written in it. Learning Zig felt like a good way to get back into systems programming, something I’ve been wanting to do after a couple of chaotic years of web development.&lt;/p&gt;
&lt;p&gt;This post is mostly about my setup, results, and the things I learned from solving the puzzles. If you’re more interested in my solutions, I’ve also uploaded my code and solution write-ups to my &lt;a href=&quot;https://github.com/rmrt1n/advent-of-code&quot;&gt;GitHub repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/aoc2024/FBG5iC0aoh-1394.webp&quot; alt=&quot;My Advent of Code results page&quot; width=&quot;1394&quot; height=&quot;1228&quot;&gt;&lt;/p&gt;
&lt;aside class=&quot;toc&quot;&gt;&lt;div&gt;
      &lt;h2 id=&quot;toc&quot;&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#toc&quot; class=&quot;header-anchor&quot;&gt;Table of Contents&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#self-imposed-constraints&quot;&gt;Self-Imposed Constraints&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#favourite-puzzles&quot;&gt;Favourite Puzzles&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#programming-patterns-and-zig-tricks&quot;&gt;Programming Patterns and Zig Tricks&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#comptime&quot;&gt;Comptime&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#optional-types&quot;&gt;Optional Types&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#grid-padding&quot;&gt;Grid Padding&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#simd-vectors&quot;&gt;SIMD Vectors&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#performance-tips&quot;&gt;Performance Tips&lt;/a&gt;&lt;ol&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#minimise-allocations&quot;&gt;Minimise Allocations&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#make-your-data-smaller&quot;&gt;Make Your Data Smaller&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#reduce-branching&quot;&gt;Reduce Branching&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#avoid-recursion&quot;&gt;Avoid Recursion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#benchmarks&quot;&gt;Benchmarks&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#reflections&quot;&gt;Reflections&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;/aside&gt;
&lt;h2 id=&quot;project-setup&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#project-setup&quot;&gt;Project Setup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There were &lt;a href=&quot;https://github.com/search?q=advent+of+code+template+lang%3Azig&amp;amp;type=repositories&quot;&gt;several Advent of Code templates in Zig&lt;/a&gt; that I looked at as a reference for my development setup, but none of them really clicked with me. I ended up just running my solutions directly using &lt;code&gt;zig run&lt;/code&gt; for the whole event. It wasn’t until after the event ended that I properly learned &lt;a href=&quot;https://ziglang.org/learn/build-system/&quot;&gt;Zig’s build system&lt;/a&gt; and reorganised my project.&lt;/p&gt;
&lt;p&gt;Here’s what the project structure looks like now:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;.
├── src
│   ├── days
│   │   ├── data
│   │   │   ├── day01.txt
│   │   │   ├── day02.txt
│   │   │   └── ...
│   │   ├── day01.zig
│   │   ├── day02.zig
│   │   └── ...
│   ├── bench.zig
│   └── run.zig
└── build.zig&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The project is powered by &lt;code&gt;build.zig&lt;/code&gt;, which defines several commands:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zig build&lt;/code&gt; - Builds all of the binaries for all optimisation modes.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zig build run&lt;/code&gt; - Runs all solutions sequentially.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zig build run -Day=XX&lt;/code&gt; - Runs the solution of the specified day only.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Benchmark&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zig build bench&lt;/code&gt; - Runs all benchmarks sequentially.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zig build bench -Day=XX&lt;/code&gt; - Runs the benchmark of the specified day only.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;zig build test&lt;/code&gt; - Runs all tests sequentially.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;zig build test -Day=XX&lt;/code&gt; - Runs the tests of the specified day only.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can also pass the optimisation mode that you want to any of the commands above with the &lt;code&gt;-Doptimize&lt;/code&gt; flag.&lt;/p&gt;
&lt;p&gt;Under the hood, &lt;code&gt;build.zig&lt;/code&gt; compiles &lt;code&gt;src/run.zig&lt;/code&gt; when you call &lt;code&gt;zig build run&lt;/code&gt;, and &lt;code&gt;src/bench.zig&lt;/code&gt; when you call &lt;code&gt;zig build bench&lt;/code&gt;. These files are templates that import the solution for a specific day from &lt;code&gt;src/days/dayXX.zig&lt;/code&gt;. For example, here’s what &lt;code&gt;src/run.zig&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/run.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; std &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;std&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; puzzle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Injected by build.zig&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; arena &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;heap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ArenaAllocator&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;heap&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;page_allocator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; arena&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; allocator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; arena&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;allocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{s}&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;puzzle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    _ &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; puzzle&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;allocator&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;debug&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#92;n&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;day&lt;/code&gt; module imported is an &lt;a href=&quot;https://ziglang.org/learn/build-system/#generating-zig&quot;&gt;anonymous import&lt;/a&gt; dynamically injected by &lt;code&gt;build.zig&lt;/code&gt; during compilation. This allows a single &lt;code&gt;run.zig&lt;/code&gt; or &lt;code&gt;bench.zig&lt;/code&gt; to be reused for all solutions. This avoids repeating boilerplate code in the solution files. Here’s a simplified version of my &lt;code&gt;build.zig&lt;/code&gt; file that shows how this works:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;build.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; std &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@import&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;std&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt;std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;void&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;standardTargetOptions&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; optimize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;standardOptimizeOption&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; run_all &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;step&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;run&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Run all days&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; day_option &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;option&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;usize&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ay&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// The `-Day` option&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Generate build targets for all 25 days.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;26&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;day&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; day_zig_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src/days/day{d:0&amp;gt;2}.zig&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;day&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Create an executable for running this specific day.&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; run_exe &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addExecutable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fmt&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;run-day{d:0&amp;gt;2}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;day&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root_source_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src/run.zig&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;optimize &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; optimize&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Inject the day-specific solution file as the anonymous module `day`.&lt;/span&gt;
        run_exe&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root_module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addAnonymousImport&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;day&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root_source_file &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; day_zig_file &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// Install the executable so it can be run.&lt;/span&gt;
        b&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;installArtifact&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;run_exe&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;My actual &lt;code&gt;build.zig&lt;/code&gt; has some extra code that builds the binaries for all optimisation modes.&lt;/p&gt;
&lt;p&gt;This setup is pretty barebones. I’ve seen &lt;a href=&quot;https://github.com/fspoettel/advent-of-code-rust&quot;&gt;other templates&lt;/a&gt; do cool things like scaffold files, download puzzle inputs, and even submit answers automatically. Since I wrote my &lt;code&gt;build.zig&lt;/code&gt; after the event ended, I didn’t get to use it while solving the puzzles. I might add these features to it if I decided to do Advent of Code again this year with Zig.&lt;/p&gt;
&lt;h2 id=&quot;self-imposed-constraints&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#self-imposed-constraints&quot;&gt;Self-Imposed Constraints&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While there are no rules to Advent of Code itself, to make things a little more interesting, I set a few constraints and rules for myself:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The code must be readable&lt;/strong&gt;.
By “readable”, I mean the code should be straightforward and easy to follow. No unnecessary abstractions. I should be able to come back to the code months later and still understand (most of) it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Solutions must be a single file&lt;/strong&gt;.
No external dependencies. No shared utilities module. Everything needed to solve the puzzle should be visible in that one solution file.&lt;/li&gt;
&lt;li&gt;
&lt;aside id=&quot;sn2&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;2.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;The original constraint was that each solution must run in under one second. As it turned out, the code was faster than I expected, so I increased the difficulty. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;strong&gt;The total runtime must be under one second&lt;/strong&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn2&quot; id=&quot;fnref2&quot;&gt;[2]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn2&quot; id=&quot;snref2&quot;&gt;[2]&lt;/a&gt;
        &lt;/sup&gt;
All solutions, when run sequentially, should finish in under one second. I want to improve my performance engineering skills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parts should be solved separately&lt;/strong&gt;.
This means: (1) no solving both parts simultaneously, and (2) no doing extra work in part one that makes part two faster. The aim of this is to get a clear idea of how long each part takes on its own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No concurrency or parallelism&lt;/strong&gt;.
Solutions must run sequentially on a single thread. This keeps the focus on the efficiency of the algorithm. I can’t speed up slow solutions by using multiple CPU cores.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No ChatGPT. No Claude. No AI help&lt;/strong&gt;.
I want to train myself, not the LLM. I can look at other people’s solutions, but only after I have given my best effort at solving the problem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Follow the constraints of the input file&lt;/strong&gt;.
The solution doesn’t have to work for all possible scenarios, but it should work for all valid inputs. If the input file only contains 8-bit unsigned integers, the solution doesn’t have to handle larger integer types.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hardcoding is allowed&lt;/strong&gt;.
For example: size of the input, number of rows and columns, etc. Since the input is known at compile-time, we can skip runtime parsing and just embed it into the program using Zig’s &lt;code&gt;@embedFile&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;aside id=&quot;sn3&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;3.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;TigerBeetle&#39;s code quality and &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md&quot;&gt;engineering principles&lt;/a&gt; are just wonderful. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Most of these constraints are designed to push me to write clearer, more performant code. I also wanted my code to look like it was taken straight from TigerBeetle’s codebase (minus the assertions).&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn3&quot; id=&quot;fnref3&quot;&gt;[3]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn3&quot; id=&quot;snref3&quot;&gt;[3]&lt;/a&gt;
        &lt;/sup&gt; Lastly, I just thought it would make the experience more fun.&lt;/p&gt;
&lt;h2 id=&quot;favourite-puzzles&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#favourite-puzzles&quot;&gt;Favourite Puzzles&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From all of the puzzles, here are my top 3 favourites:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Day 6: Guard Gallivant&lt;/strong&gt; - This is my slowest day (in benchmarks), but also the one I learned the most from. Some of these learnings include: using vectors to represent directions, padding 2D grids, metadata packing, system endianness, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 17: Chronospatial Computer&lt;/strong&gt; - I love reverse engineering puzzles. I used to do a lot of these in CTFs during my university days. The best thing I learned from this day is the realisation that we can use different integer bases to optimise data representation. This helped improve my runtimes in the later days 22 and 23.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Day 21: Keypad Conundrum&lt;/strong&gt; - This one was fun. My gut told me that it can be solved greedily by always choosing the best move. It was right. Though I did have to scroll Reddit for a bit to figure out the step I was missing, which was that you have to visit the farthest keypads first. This is also my longest solution file (almost 400 lines) because I hardcoded the best-moves table.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Honourable mention:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Day 24: Crossed Wires&lt;/strong&gt; - Another reverse engineering puzzle. Confession: I didn’t solve this myself during the event. After 23 brutal days, my brain was too tired, so I copied a random Python solution from Reddit. When I retried it later, it turned out to be pretty fun. I still couldn’t find a solution I was satisfied with though.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&quot;programming-patterns-and-zig-tricks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#programming-patterns-and-zig-tricks&quot;&gt;Programming Patterns and Zig Tricks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;During the event, I learned a lot about Zig and performance, and also developed some personal coding conventions. Some of these are Zig-specific, but most are universal and can be applied across languages. This section covers general programming and Zig patterns I found useful. The next section will focus on performance-related tips.&lt;/p&gt;
&lt;h3 id=&quot;comptime&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#comptime&quot;&gt;Comptime&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Zig’s flagship feature, &lt;a href=&quot;https://kristoff.it/blog/what-is-zig-comptime/&quot;&gt;&lt;code&gt;comptime&lt;/code&gt;&lt;/a&gt;, is surprisingly useful. I knew Zig uses it for generics and that people do clever metaprogramming with it, but I didn’t expect to be using it so often myself.&lt;/p&gt;
&lt;p&gt;My main use for &lt;code&gt;comptime&lt;/code&gt; was to generate puzzle-specific types. All my solution files follow the same structure, with a &lt;code&gt;DayXX&lt;/code&gt; function that takes some parameters (usually the input length) and returns a puzzle-specific type, e.g.:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day01.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;Day01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comptime&lt;/span&gt; length&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;usize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;type&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Self &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@This&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        
        left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u32&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;length&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u32&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

        &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;Self&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This lets me instantiate the type with a size that matches my input:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day01.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Here, `Day01` is called with the size of my actual input.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;pub&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;_&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Allocator&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; is_run&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; input &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@embedFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;./data/day01.txt&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; puzzle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Day01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Here, `Day01` is called with the size of my test input.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;day 01 part 1 sample 1&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; puzzle &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;Day01&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sample_input&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This allows me to reuse logic across different inputs while still hardcoding the array sizes. Without &lt;code&gt;comptime&lt;/code&gt;, I have to either create a separate function for all my different inputs or dynamically allocate memory because I can’t hardcode the array size.&lt;/p&gt;
&lt;p&gt;I also used &lt;code&gt;comptime&lt;/code&gt; to shift some computation to compile-time to reduce runtime overhead. For example, on day 4, I needed a function to check whether a string matches either &lt;code&gt;&quot;XMAS&quot;&lt;/code&gt; or its reverse, &lt;code&gt;&quot;SAMX&quot;&lt;/code&gt;. A pretty simple function that you can write as a one-liner in Python:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example.py&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-python&quot;&gt;&lt;code class=&quot;language-python&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pattern&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; pattern &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; target &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; pattern&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn4&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;4.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;You can implement this function without any allocation by mutating the string in place or by iterating over it twice, which is probably faster than my current implementation. I kept it as-is as a reminder of what &lt;code&gt;comptime&lt;/code&gt; can do. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Typically, a function like this requires some dynamic allocation to create the reversed string, since the length of the string is only known at runtime.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn4&quot; id=&quot;fnref4&quot;&gt;[4]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn4&quot; id=&quot;snref4&quot;&gt;[4]&lt;/a&gt;
        &lt;/sup&gt; For this puzzle, since the words to reverse are known at compile-time, we can do something like this:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day04.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;matches&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;comptime&lt;/span&gt; word&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slice&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; reversed&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;word&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;len&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;@memcpy&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;reversed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; word&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;reverse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;reversed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; word&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;eql&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;reversed&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; slice&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;aside id=&quot;sn5&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;5.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;As a bonus, I was curious as to what this looks like compiled, so I listed all the functions in this binary in GDB and found:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;72: static bool day04.Day04(140).matches__anon_19741;
72: static bool day04.Day04(140).matches__anon_19750;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does generate separate functions! &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This creates a separate function for each word I want to reverse.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn5&quot; id=&quot;fnref5&quot;&gt;[5]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn5&quot; id=&quot;snref5&quot;&gt;[5]&lt;/a&gt;
        &lt;/sup&gt; Each function has an array with the same size as the word to reverse. This removes the need for dynamic allocation and makes the code run faster. As a bonus, Zig also warns you when this word isn’t compile-time known, so you get an immediate error if you pass in a runtime value.&lt;/p&gt;
&lt;h3 id=&quot;optional-types&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#optional-types&quot;&gt;Optional Types&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A common pattern in C is to return special sentinel values to denote missing values or errors, e.g. &lt;code&gt;-1&lt;/code&gt;, &lt;code&gt;0&lt;/code&gt;, or &lt;code&gt;NULL&lt;/code&gt;. In fact, I did this on day 13 of the challenge:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day13.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// We won&#39;t ever get 0 as a result, so we use it as a sentinel error value.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;count_tokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i64&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; numerator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; denumerator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;@as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numerator &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; denumerator &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; numerator &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; denumerator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// Then in the caller, skip if the return value is 0.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count_tokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;continue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This works, but it’s easy to forget to check for those values, or worse, to accidentally treat them as valid results. Zig improves on this with &lt;a href=&quot;https://ziglang.org/documentation/master/#Optionals&quot;&gt;optional types&lt;/a&gt;. If a function might not return a value, you can return &lt;code&gt;?T&lt;/code&gt; instead of &lt;code&gt;T&lt;/code&gt;. This also forces the caller to handle the &lt;code&gt;null&lt;/code&gt; case. Unlike C, &lt;code&gt;null&lt;/code&gt; isn’t a pointer but a more general concept. Zig treats &lt;code&gt;null&lt;/code&gt; as the absence of a value for any type, just like Rust’s &lt;a href=&quot;https://doc.rust-lang.org/std/option/&quot;&gt;&lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;count_tokens&lt;/code&gt; function can be refactored to:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day13.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Return null instead if there&#39;s no valid result.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;count_tokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i64&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; numerator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; denumerator &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@abs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;@as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i32&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; a&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;*&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;numerator &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; denumerator &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; numerator &lt;span class=&quot;token operator&quot;&gt;/&lt;/span&gt; denumerator&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// The caller is now forced to handle the null case.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;count_tokens&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;a&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; b&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; p&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;n_tokens&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// logic only runs when n_tokens is not null.&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Zig also has a concept of &lt;a href=&quot;https://ziglang.org/documentation/master/#Error-Union-Type&quot;&gt;error unions&lt;/a&gt;, where a function can return either a value or an error. In Rust, this is &lt;a href=&quot;https://doc.rust-lang.org/std/result/&quot;&gt;&lt;code&gt;Result&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt;. You could also use error unions instead of optionals for &lt;code&gt;count_tokens&lt;/code&gt;; Zig doesn’t force a single approach. I come from Clojure, where returning &lt;code&gt;nil&lt;/code&gt; for an error or missing value is common.&lt;/p&gt;
&lt;h3 id=&quot;grid-padding&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#grid-padding&quot;&gt;Grid Padding&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This year has a lot of 2D grid puzzles (arguably too many). A common feature of grid-based algorithms is the out-of-bounds check. Here’s what it usually looks like:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;dfs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u32&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Bounds check here.&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;x &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;&amp;gt;=&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;len &lt;span class=&quot;token keyword&quot;&gt;or&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;&amp;gt;=&lt;/span&gt; map&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;len&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visited&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    map&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;visited&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u32&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;directions&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; direction&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;dfs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is a typical recursive DFS function. After doing a lot of this, I discovered a nice trick that not only improves code readability, but also its performance. The trick here is to pad the grid with sentinel characters that mark out-of-bounds areas, i.e. add a border to the grid.&lt;/p&gt;
&lt;p&gt;Here’s an example from day 6:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Original map:               With borders added:
                            ************
....#.....                  *....#.....*
.........#                  *.........#*
..........                  *..........*
..#.......                  *..#.......*
.......#..        -&amp;gt;        *.......#..*
..........                  *..........*
.#..^.....                  *.#..^.....*
........#.                  *........#.*
#.........                  *#.........*
......#...                  *......#...*
                            ************&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can use any value for the border, as long as it doesn’t conflict with valid values in the grid. With the border in place, the bounds check becomes a simple equality comparison:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; border &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token char&quot;&gt;&#39;*&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;dfs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;i8&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u32&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; x&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; y &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;x&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;y&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; border&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// We are out of bounds&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is much more readable than the previous code. Plus, it’s also faster since we’re only doing one equality check instead of four range checks.&lt;/p&gt;
&lt;p&gt;That said, this isn’t a one-size-fits-all solution. This only works for algorithms that traverse the grid one step at a time. If your logic jumps multiple tiles, it can still go out of bounds (except if you increase the width of the border to account for this). This approach also uses a bit more memory than the regular approach as you have to store more characters.&lt;/p&gt;
&lt;h3 id=&quot;simd-vectors&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#simd-vectors&quot;&gt;SIMD Vectors&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This could also go in the performance section, but I’m including it here because the biggest benefit I get from using SIMD in Zig is the improved code readability. Because Zig has first-class &lt;a href=&quot;https://ziglang.org/documentation/master/#Vectors&quot;&gt;support for vector types&lt;/a&gt;, you can write elegant and readable code that also happens to be faster.&lt;/p&gt;
&lt;aside id=&quot;sn6&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;6.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Well, not always. The number of SIMD instructions depends on the machine&#39;s native SIMD size. If the length of the vector exceeds it, Zig will compile it into multiple SIMD instructions. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;If you’re not familiar with vectors, they are a special collection type used for &lt;a href=&quot;https://en.wikipedia.org/wiki/Single_instruction,_multiple_data&quot;&gt;Single instruction, multiple data (SIMD)&lt;/a&gt; operations. SIMD allows you to perform computation on multiple values in parallel using only a single CPU instruction, which often leads to some performance boosts.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn6&quot; id=&quot;fnref6&quot;&gt;[6]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn6&quot; id=&quot;snref6&quot;&gt;[6]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I mostly use vectors to represent positions and directions, e.g. for traversing a grid. Instead of writing code like this:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;next_position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; position&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;You can represent &lt;code&gt;position&lt;/code&gt; and &lt;code&gt;direction&lt;/code&gt; as 2-element vectors and write code like this:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;example.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;next_position &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; position &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This is much nicer than the previous version!&lt;/p&gt;
&lt;p&gt;Day 25 is another good example of a problem that can be solved elegantly using vectors:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day25.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;locks&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;lock&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// lock is a vector&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;keys&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;key&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// key is also a vector&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fitted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; lock &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; key &lt;span class=&quot;token operator&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@as&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;@Vector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@splat&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; is_overlap &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@reduce&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Or&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fitted&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
        result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@intFromBool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;is_overlap&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Expressing the logic as vector operations makes the code cleaner since you don’t have to write loops and conditionals as you typically would in a traditional approach.&lt;/p&gt;
&lt;h2 id=&quot;performance-tips&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#performance-tips&quot;&gt;Performance Tips&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The tips below are general performance techniques that often help, but like most things in software engineering, “it depends”. These might work 80% of the time, but performance is often highly context-specific. You should benchmark your code instead of blindly following what other people say.&lt;/p&gt;
&lt;aside id=&quot;sn7&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;7.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Here&#39;s a nice &lt;a href=&quot;https://cprimozic.net/blog/optimizing-advent-of-code-2024/&quot;&gt;post on optimising day 9&#39;s solution with Rust&lt;/a&gt;. It&#39;s a good read if you&#39;re into performance engineering or Rust techniques. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;This section would’ve been more fun with concrete examples, step-by-step optimisations, and benchmarks, but that would’ve made the post way too long. Hopefully, I’ll get to write something like that in the future.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn7&quot; id=&quot;fnref7&quot;&gt;[7]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn7&quot; id=&quot;snref7&quot;&gt;[7]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;h3 id=&quot;minimise-allocations&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#minimise-allocations&quot;&gt;Minimise Allocations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Whenever possible, prefer static allocation. Static allocation is cheaper since it just involves moving the stack pointer vs dynamic allocation which has more overhead from the allocator machinery. That said, it’s not always the right choice since it has some limitations, e.g. stack size is limited, memory size must be compile-time known, its lifetime is tied to the current stack frame, etc.&lt;/p&gt;
&lt;p&gt;If you need to do dynamic allocations, try to reduce the number of times you call the allocator. The number of allocations you do matters more than the amount of memory you allocate. More allocations mean more bookkeeping, synchronisation, and sometimes syscalls.&lt;/p&gt;
&lt;p&gt;A simple but effective way to reduce allocations is to reuse buffers, whether they’re statically or dynamically allocated. Here’s an example from day 10. For each trail head, we want to create a set of trail ends reachable from it. The naive approach is to allocate a new set every iteration:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day10.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;trail_heads&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;trail_head&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; trail_ends &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AutoHashMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allocator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; trail_ends&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Set building logic...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;What you can do instead is to allocate the set once before the loop. Then, each iteration, you reuse the set by emptying it without freeing the memory. For Zig’s &lt;code&gt;std.AutoHashMap&lt;/code&gt;, this can be done using the &lt;code&gt;clearRetainingCapacity&lt;/code&gt; method:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day10.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; trail_ends &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AutoHashMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;void&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allocator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; trail_ends&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;trail_heads&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;items&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;trail_head&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    trail_ends&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearRetainingCapacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    
    &lt;span class=&quot;token comment&quot;&gt;// Set building logic...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;If you use static arrays, you can also just overwrite existing data instead of clearing it.&lt;/p&gt;
&lt;p&gt;A step up from this is to &lt;a href=&quot;https://en.wikipedia.org/wiki/Multiple_buffering&quot;&gt;reuse multiple buffers&lt;/a&gt;. The simplest form of this is to reuse two buffers, i.e. double buffering. Here’s an example from day 11:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day11.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Initialise two hash maps that we&#39;ll alternate between.&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; frequencies&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AutoHashMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; frequencies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;AutoHashMap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin-type keyword&quot;&gt;u64&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;init&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;allocator&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;i&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; frequencies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deinit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; id&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;usize&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stones&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;stone&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt; frequencies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;stone&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;n_blinks&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;_&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; old_frequencies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;frequencies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; new_frequencies &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;frequencies&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;id &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    id &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;defer&lt;/span&gt; old_frequencies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;clearRetainingCapacity&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// Do stuff with both maps...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Here we have two maps to count the frequencies of stones across iterations. Each iteration will build up &lt;code&gt;new_frequencies&lt;/code&gt; with the values from &lt;code&gt;old_frequencies&lt;/code&gt;. Doing this reduces the number of allocations to just 2 (the number of buffers). The tradeoff here is that it makes the code slightly more complex.&lt;/p&gt;
&lt;h3 id=&quot;make-your-data-smaller&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#make-your-data-smaller&quot;&gt;Make Your Data Smaller&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A performance tip people say is to have “mechanical sympathy”. Understand how your code is processed by your computer. An example of this is to structure your data so it works better with your CPU. For example, keep related data close in memory to take advantage of &lt;a href=&quot;https://en.algorithmica.org/hpc/external-memory/locality/&quot;&gt;cache locality&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Reducing the size of your data helps with this. Smaller data means more of it can fit in cache. One way to shrink your data is through &lt;a href=&quot;https://www.cs.cornell.edu/courses/cs3410/2024fa/notes/bitpack.html&quot;&gt;bit packing&lt;/a&gt;. This depends heavily on your specific data, so you’ll need to use your judgement to tell whether this would work for you. I’ll just share some examples that worked for me.&lt;/p&gt;
&lt;p&gt;The first example is in day 6 part two, where you have to detect a loop, which happens when you revisit a tile from the same direction as before. To track this, you could use a map or a set to store the tiles and visited directions. A more efficient option is to store this direction metadata in the tile itself.&lt;/p&gt;
&lt;p&gt;There are only four tile types, which means you only need two bits to represent the tile types as an enum. If the enum size is one byte, here’s what the tiles look like in memory:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;.obstacle -&amp;gt; 00000000
.path     -&amp;gt; 00000001
.visited  -&amp;gt; 00000010
.path     -&amp;gt; 00000011&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you can see, the upper six bits are unused. We can store the direction metadata in the upper four bits. One bit for each direction. If a bit is set, it means that we’ve already visited the tile in this direction. Here’s an illustration of the memory layout:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;        direction metadata   tile type
           ┌─────┴─────┐   ┌─────┴─────┐
┌────────┬─┴─┬───┬───┬─┴─┬─┴─┬───┬───┬─┴─┐
│ Tile:  │ 1 │ 0 │ 0 │ 0 │ 0 │ 0 │ 1 │ 0 │
└────────┴─┬─┴─┬─┴─┬─┴─┬─┴───┴───┴───┴───┘
   up bit ─┘   │   │   └─ left bit
    right bit ─┘ down bit&lt;/code&gt;&lt;/pre&gt;
&lt;aside id=&quot;sn8&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;8.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;One thing about packed structs is that their layout is dependent on the system endianness. Most modern systems are little-endian, so the memory layout I showed is actually reversed. Thankfully, Zig has some useful functions to convert between endianness like &lt;code&gt;std.mem.nativeToBig&lt;/code&gt;, which makes working with packed structs easier. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;If your language supports struct packing, you can express this layout directly:&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn8&quot; id=&quot;fnref8&quot;&gt;[8]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn8&quot; id=&quot;snref8&quot;&gt;[8]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day06.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;Tile&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;packed&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;struct&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;TileType&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;enum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;u4&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; obstacle&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; visited&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; exit &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

    up&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;u1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    right&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;u1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    down&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;u1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    left&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;u1&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    tile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TileType&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Doing this avoids extra allocations and improves cache locality. Since the directions metadata is colocated with the tile type, all of them can fit together in cache. Accessing the directions just requires some bitwise operations instead of having to fetch them from another region of memory.&lt;/p&gt;
&lt;aside id=&quot;sn9&quot; class=&quot;sidenote&quot;&gt;
               &lt;span class=&quot;sidenote-number&quot;&gt;9.&lt;/span&gt;
               &lt;div class=&quot;sidenote-content&quot;&gt;&lt;p&gt;Technically, you can store 2-digit base 26 numbers in a &lt;code&gt;u10&lt;/code&gt;, as there are only &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: -0.05ex;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;3.25ex&quot; height=&quot;2.005ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -864 1436.6 886&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(1033,393.1) scale(0.707)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;26&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; possible numbers. Most systems usually pad values by byte size, so &lt;code&gt;u10&lt;/code&gt; will still be stored as &lt;code&gt;u16&lt;/code&gt;, which is why I just went straight for it. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#snref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
            &lt;/aside&gt;&lt;p&gt;Another way to do this is to represent your data using &lt;a href=&quot;https://en.wikipedia.org/wiki/Numeral_system#Positional_systems_in_detail&quot;&gt;alternate number bases&lt;/a&gt;. Here’s an example from day 23. Computers are represented as two-character strings made up of only lowercase letters, e.g. &lt;code&gt;&quot;bc&quot;&lt;/code&gt;, &lt;code&gt;&quot;xy&quot;&lt;/code&gt;, etc. Instead of storing this as a &lt;code&gt;[2]u8&lt;/code&gt; array, you can convert it into a base-26 number and store it as a &lt;code&gt;u16&lt;/code&gt;.&lt;sup class=&quot;footnote-ref&quot;&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fn9&quot; id=&quot;fnref9&quot;&gt;[9]&lt;/a&gt;
        &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#sn9&quot; id=&quot;snref9&quot;&gt;[9]&lt;/a&gt;
        &lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Here’s the idea: map &lt;code&gt;&#39;a&#39;&lt;/code&gt; to 0, &lt;code&gt;&#39;b&#39;&lt;/code&gt; to 1, up to &lt;code&gt;&#39;z&#39;&lt;/code&gt; as 25. Each character in the string becomes a digit in the base-26 number. For example,  &lt;code&gt;&quot;bc&quot;&lt;/code&gt; ( &lt;code&gt;[2]u8{ &#39;b&#39;, &#39;c&#39; }&lt;/code&gt;) becomes the base-10 number 28 (&lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: -0.186ex;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;14.33ex&quot; height=&quot;1.692ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -666 6334 748&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;31&quot; xlink:href=&quot;#MJX-TEX-N-31&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mi&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;use data-c=&quot;D7&quot; xlink:href=&quot;#MJX-TEX-I-D7&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(1278,0)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mo&quot; transform=&quot;translate(2500.2,0)&quot;&gt;&lt;use data-c=&quot;2B&quot; xlink:href=&quot;#MJX-TEX-N-2B&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(3500.4,0)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mo&quot; transform=&quot;translate(4278.2,0)&quot;&gt;&lt;use data-c=&quot;3D&quot; xlink:href=&quot;#MJX-TEX-N-3D&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(5334,0)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;38&quot; xlink:href=&quot;#MJX-TEX-N-38&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;×&lt;/mi&gt;&lt;mn&gt;26&lt;/mn&gt;&lt;mo&gt;+&lt;/mo&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mo&gt;=&lt;/mo&gt;&lt;mn&gt;28&lt;/mn&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt;). If we represent this using the base-64 character set, it becomes 12 (&lt;code&gt;&#39;b&#39;&lt;/code&gt; = 1, &lt;code&gt;&#39;c&#39;&lt;/code&gt; = 2).&lt;/p&gt;
&lt;p&gt;While they take the same amount of space (2 bytes), a &lt;code&gt;u16&lt;/code&gt; has some benefits over a &lt;code&gt;[2]u8&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It fits in a single register, whereas you need two for the array.&lt;/li&gt;
&lt;li&gt;Comparison is faster as there is only a single value to compare.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;reduce-branching&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#reduce-branching&quot;&gt;Reduce Branching&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I won’t explain branchless programming here; &lt;a href=&quot;https://en.algorithmica.org/hpc/pipelining/branchless/&quot;&gt;Algorithmica&lt;/a&gt; explains it way better than I can. While modern compilers are often smart enough to compile away branches, they don’t catch everything. I still recommend writing branchless code whenever it makes sense. It also has the added benefit of reducing the number of &lt;a href=&quot;https://www.rfleury.com/p/the-codepath-combinatoric-explosion&quot;&gt;codepaths in your program&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Again, since performance is very context-dependent, I’ll just show you some patterns I use. Here’s one that comes up often:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day02.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_valid_report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Instead of the branch, cast the bool into an integer directly:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day02.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;result &lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@intFromBool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is_valid_report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Another example is from day 6 (again!). Recall that to know if a tile has been visited from a certain direction, we have to check its direction bit. Here’s one way to do it:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day06.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;has_visited&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Tile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;switch&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;direction&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;up &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;up &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;right &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;down &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;down &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; self&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;left &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This works, but it introduces a few branches. We can make it branchless using bitwise operations:&lt;/p&gt;
&lt;div class=&quot;codeblock&quot;&gt;
      &lt;div&gt;
        &lt;span&gt;src/days/day06.zig&lt;/span&gt;
        &lt;button onclick=&quot;copy(this)&quot;&gt;Copy&lt;/button&gt;
      &lt;/div&gt;
&lt;pre class=&quot;language-zig&quot;&gt;&lt;code class=&quot;language-zig&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;fn&lt;/span&gt; &lt;span class=&quot;token definitions&quot;&gt;has_visited&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tile&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Tile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;bool&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; int_tile &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; std&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;mem&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;nativeToBig&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin-type keyword&quot;&gt;u8&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;@bitCast&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;tile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; mask &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; direction&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;mask&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; bits &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; int_tile &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0xff&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;// Get only the direction bits&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; bits &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt; mask &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; mask&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;p&gt;While this is arguably cryptic and less readable, it does perform better than the switch version.&lt;/p&gt;
&lt;h3 id=&quot;avoid-recursion&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#avoid-recursion&quot;&gt;Avoid Recursion&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The final performance tip is to prefer iterative code over recursion. Recursive functions bring the overhead of allocating stack frames. While recursive code is more elegant, it’s also often slower unless your language’s compiler can optimise it away, e.g. via tail-call optimisation. As far as I know, Zig doesn’t have this, though I might be wrong.&lt;/p&gt;
&lt;p&gt;Recursion also has the risk of causing a stack overflow if the execution isn’t bounded. This is why code that is mission- or safety-critical avoids recursion entirely. It’s in TigerBeetle’s &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md&quot;&gt;TIGERSTYLE&lt;/a&gt; and also NASA’s &lt;a href=&quot;https://spinroot.com/gerard/pdf/P10.pdf&quot;&gt;Power of Ten&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Iterative code can be harder to write in some cases, e.g. DFS maps naturally to recursion, but most of the time it is significantly faster, more predictable, and safer than the recursive alternative.&lt;/p&gt;
&lt;h2 id=&quot;benchmarks&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#benchmarks&quot;&gt;Benchmarks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I ran benchmarks for all 25 solutions in each of Zig’s optimisation modes. You can find the full results and the benchmark script in my &lt;a href=&quot;https://github.com/rmrt1n/advent-of-code&quot;&gt;GitHub repository&lt;/a&gt;. All benchmarks were done on an &lt;a href=&quot;https://en.wikipedia.org/wiki/Apple_M3&quot;&gt;Apple M3 Pro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;As expected, &lt;code&gt;ReleaseFast&lt;/code&gt; produced the best result with a total runtime of &lt;strong&gt;85.1 ms&lt;/strong&gt;. I’m quite happy with this, considering the two constraints that limited the number of optimisations I can do to the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parts should be solved separately&lt;/strong&gt; - Some days can be solved in a single go, e.g. day 10 and day 13, which could’ve saved a few milliseconds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No concurrency or parallelism&lt;/strong&gt; - My slowest days are the compute-heavy days that are very easily parallelisable, e.g. day 6, day 19, and day 22. Without this constraint, I can probably reach sub-20 milliseconds total(?), but that’s for another time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can see the full benchmarks for &lt;code&gt;ReleaseFast&lt;/code&gt; in the table below:&lt;/p&gt;
&lt;div style=&quot;overflow-x: auto; border: 0.05rem solid var(--color-border);&quot;&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Day&lt;/th&gt;
&lt;th&gt;Title&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Parsing (µs)&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Part 1 (µs)&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Part 2 (µs)&lt;/th&gt;
&lt;th style=&quot;text-align:right&quot;&gt;Total (µs)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Historian Hysteria&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;23.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;15.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;41.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Red-Nosed Reports&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;42.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;11.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;54.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Mull it Over&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;7.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;16.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;23.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Ceres Search&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;5.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;5.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Print Queue&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;22.3&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;4.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;26.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Guard Gallivant&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;14.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;25.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;24,331.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;24,370.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Bridge Repair&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;72.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;321.4&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;9,620.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;10,014.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Resonant Collinearity&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;3.3&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;13.4&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;19.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Disk Fragmenter&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;12.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;137.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;151.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Hoof It&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;29.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;27.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;59.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Plutonian Pebbles&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.1&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;43.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2,115.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;2,159.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Garden Groups&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;6.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;164.4&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;249.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;420.3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Claw Contraption&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;14.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;14.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Restroom Redoubt&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;13.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;13.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Warehouse Woes&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;14.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;228.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;458.3&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;701.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Reindeer Maze&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;12.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2,480.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;9,010.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;11,504.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Chronospatial Computer&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.1&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;44.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;44.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;RAM Run&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;35.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;15.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;33.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;85.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Linen Layout&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;10.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;11,890.8&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;11,908.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;23,810.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Race Condition&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;48.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;54.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;54.2&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;157.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Keypad Conundrum&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;1.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;22.4&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;24.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Monkey Market&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;20.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;11,227.7&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;11,248.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;LAN Party&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;13.6&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;22.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;2.5&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;38.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Crossed Wires&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;5.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;41.3&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;14.3&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;60.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;Code Chronicle&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;24.9&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;0.0&lt;/td&gt;
&lt;td style=&quot;text-align:right&quot;&gt;&lt;strong&gt;24.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;A weird thing I found when benchmarking is that for day 6 part two, &lt;code&gt;ReleaseSafe&lt;/code&gt; actually ran faster than &lt;code&gt;ReleaseFast&lt;/code&gt; (13,189.0 µs vs 24,370.7 µs). Their outputs are the same, but for some reason, &lt;code&gt;ReleaseSafe&lt;/code&gt; is faster even with the safety checks still intact.&lt;/p&gt;
&lt;p&gt;The Zig compiler is still very much a moving target, so I don’t want to dig too deep into this, as I’m guessing this might be a bug in the compiler. This weird behaviour might just disappear after a few compiler version updates.&lt;/p&gt;
&lt;h2 id=&quot;reflections&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#reflections&quot;&gt;Reflections&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Looking back, I’m really glad I decided to do Advent of Code and followed through to the end. I learned a lot of things. Some are useful in my professional work, some are more like random bits of trivia. Going with Zig was a good choice too. The language is small, simple, and gets out of your way. I learned more about algorithms and concepts than the language itself.&lt;/p&gt;
&lt;p&gt;Besides what I’ve already mentioned earlier, here are some examples of the things I learned:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The concept of &lt;a href=&quot;https://en.wikipedia.org/wiki/Taxicab_geometry&quot;&gt;Manhattan distance&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm&quot;&gt;Cliques&lt;/a&gt; and the &lt;a href=&quot;https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm&quot;&gt;Bron-Kerbosch algorithm&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;a href=&quot;https://en.wikipedia.org/wiki/Adder_(electronics)#Ripple-carry_adder&quot;&gt;ripple-carry adder circuit&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LaTeX math syntax with &lt;a href=&quot;https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/writing-mathematical-expressions&quot;&gt;MathJax&lt;/a&gt;, which I used in my write-ups.&lt;/li&gt;
&lt;li&gt;In Python, you can use &lt;a href=&quot;https://www.alpinewerk.com/posts/complex/&quot;&gt;complex numbers to traverse 2D grids&lt;/a&gt;!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of my self-imposed constraints and rules ended up being helpful. I can still (mostly) understand the code I wrote a few months ago. Putting all of the code in a single file made it easier to read since I don’t have to context switch to other files all the time.&lt;/p&gt;
&lt;p&gt;However, some of them did backfire a bit, e.g. the two constraints that limit how I can optimise my code. Another one is the “hardcoding allowed” rule. I used a lot of magic numbers, which helped to improve performance, but I didn’t document them, so after a while, I don’t even remember how I got them. I’ve since gone back and added explanations in my write-ups, but next time I’ll remember to at least leave comments.&lt;/p&gt;
&lt;p&gt;One constraint I’ll probably remove next time is the no concurrency rule. It’s the biggest contributor to the total runtime of my solutions. I don’t do a lot of concurrent programming, even though my main language at work is Go, so next time it might be a good idea to use Advent of Code to level up my concurrency skills.&lt;/p&gt;
&lt;p&gt;I also spent way more time on these puzzles than I originally expected. I optimised and rewrote my code multiple times. I also rewrote my write-ups a few times to make them easier to read. This is by far my longest side project yet. It’s a lot of fun, but it also takes a lot of time and effort. I almost gave up on the write-ups (and this blog post) because I don’t want to explain my awful day 15 and day 16 code. I ended up taking a break for a few months before finishing it, which is why this post is published in August lol.&lt;/p&gt;
&lt;p&gt;Just for fun, here’s a photo of some of my notebook sketches that helped me visualise my solutions. See if you can guess which days these are from:&lt;/p&gt;
&lt;p&gt;&lt;img loading=&quot;lazy&quot; decoding=&quot;async&quot; src=&quot;https://ryanmartin.me/articles/aoc2024/hWE070SOvc-1600.webp&quot; alt=&quot;Photos of my notebook sketches&quot; width=&quot;1600&quot; height=&quot;1600&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;what%E2%80%99s-next%3F&quot; tabindex=&quot;-1&quot;&gt;&lt;a class=&quot;header-anchor&quot; href=&quot;https://ryanmartin.me/articles/aoc2024/#what%E2%80%99s-next%3F&quot;&gt;What’s Next?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;So… would I do it again? Probably, though I’m not making any promises. If I do join this year, I’ll probably stick with Zig. I had my eyes on Zig since the start of 2024, so Advent of Code was the perfect excuse to learn it. This year, there aren’t any languages in particular that caught my eye, so I’ll just keep using Zig, especially since I have a proper setup ready.&lt;/p&gt;
&lt;p&gt;If you haven’t tried Advent of Code, I highly recommend checking it out this year. It’s a great excuse to learn a new language, improve your problem-solving skills, or just learn something new. If you’re eager, you can also do the &lt;a href=&quot;https://adventofcode.com/2024/events&quot;&gt;previous years’ puzzles&lt;/a&gt; as they’re still available.&lt;/p&gt;
&lt;p&gt;One of the best aspects of Advent of Code is the community. The &lt;a href=&quot;https://adventofcode.com/2024/events&quot;&gt;Advent of Code subreddit&lt;/a&gt; is a great place for discussion. You can ask questions and also see other people’s solutions. Some people also post really cool visualisations like &lt;a href=&quot;https://www.reddit.com/r/adventofcode/s/nylBX7xFbp&quot;&gt;this one&lt;/a&gt;. They also have &lt;a href=&quot;https://www.reddit.com/r/adventofcode/?f=flair_name%3A%22Meme%2FFunny%22&quot;&gt;memes!&lt;/a&gt;&lt;/p&gt;
&lt;hr class=&quot;footnotes-sep asterism&quot;&gt;
&lt;section class=&quot;footnotes&quot; aria-label=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn1&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;I failed my first attempt horribly with &lt;a href=&quot;https://clojure.org/&quot;&gt;Clojure&lt;/a&gt; during Advent of Code 2023. Once I reached the later half of the event, I just couldn’t solve the problems with a purely functional style. I could’ve pushed through using imperative code, but I stubbornly chose not to and gave up… &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref1&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn2&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;The original constraint was that each solution must run in under one second. As it turned out, the code was faster than I expected, so I increased the difficulty. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref2&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn3&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;TigerBeetle’s code quality and &lt;a href=&quot;https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md&quot;&gt;engineering principles&lt;/a&gt; are just wonderful. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref3&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn4&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;You can implement this function without any allocation by mutating the string in place or by iterating over it twice, which is probably faster than my current implementation. I kept it as-is as a reminder of what &lt;code&gt;comptime&lt;/code&gt; can do. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref4&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn5&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;As a bonus, I was curious as to what this looks like compiled, so I listed all the functions in this binary in GDB and found:&lt;/p&gt;
&lt;pre class=&quot;language-plaintext&quot;&gt;&lt;code class=&quot;language-plaintext&quot;&gt;72: static bool day04.Day04(140).matches__anon_19741;
72: static bool day04.Day04(140).matches__anon_19750;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does generate separate functions! &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref5&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn6&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Well, not always. The number of SIMD instructions depends on the machine’s native SIMD size. If the length of the vector exceeds it, Zig will compile it into multiple SIMD instructions. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref6&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn7&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Here’s a nice &lt;a href=&quot;https://cprimozic.net/blog/optimizing-advent-of-code-2024/&quot;&gt;post on optimising day 9’s solution with Rust&lt;/a&gt;. It’s a good read if you’re into performance engineering or Rust techniques. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref7&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn8&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;One thing about packed structs is that their layout is dependent on the system endianness. Most modern systems are little-endian, so the memory layout I showed is actually reversed. Thankfully, Zig has some useful functions to convert between endianness like &lt;code&gt;std.mem.nativeToBig&lt;/code&gt;, which makes working with packed structs easier. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref8&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id=&quot;fn9&quot; class=&quot;footnote-item&quot;&gt;&lt;p&gt;Technically, you can store 2-digit base 26 numbers in a &lt;code&gt;u10&lt;/code&gt;, as there are only &lt;mjx-container class=&quot;MathJax&quot; jax=&quot;SVG&quot; style=&quot;position: relative;&quot;&gt;&lt;svg style=&quot;vertical-align: -0.05ex;&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot; width=&quot;3.25ex&quot; height=&quot;2.005ex&quot; role=&quot;img&quot; focusable=&quot;false&quot; viewBox=&quot;0 -864 1436.6 886&quot; xmlns:xlink=&quot;http://www.w3.org/1999/xlink&quot; aria-hidden=&quot;true&quot;&gt;&lt;g stroke=&quot;currentColor&quot; fill=&quot;currentColor&quot; stroke-width=&quot;0&quot; transform=&quot;scale(1,-1)&quot;&gt;&lt;g data-mml-node=&quot;math&quot;&gt;&lt;g data-mml-node=&quot;msup&quot;&gt;&lt;g data-mml-node=&quot;mn&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;use data-c=&quot;36&quot; xlink:href=&quot;#MJX-TEX-N-36&quot; transform=&quot;translate(500,0)&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;g data-mml-node=&quot;mn&quot; transform=&quot;translate(1033,393.1) scale(0.707)&quot;&gt;&lt;use data-c=&quot;32&quot; xlink:href=&quot;#MJX-TEX-N-32&quot;&gt;&lt;/use&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;mjx-assistive-mml unselectable=&quot;on&quot; display=&quot;inline&quot;&gt;&lt;math xmlns=&quot;http://www.w3.org/1998/Math/MathML&quot;&gt;&lt;msup&gt;&lt;mn&gt;26&lt;/mn&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;/msup&gt;&lt;/math&gt;&lt;/mjx-assistive-mml&gt;&lt;/mjx-container&gt; possible numbers. Most systems usually pad values by byte size, so &lt;code&gt;u10&lt;/code&gt; will still be stored as &lt;code&gt;u16&lt;/code&gt;, which is why I just went straight for it. &lt;a href=&quot;https://ryanmartin.me/articles/aoc2024/#fnref9&quot; class=&quot;footnote-backref&quot;&gt;↩︎&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;svg style=&quot;display: none;&quot; id=&quot;MJX-SVG-global-cache&quot;&gt;&lt;defs&gt;&lt;path id=&quot;MJX-TEX-N-32&quot; d=&quot;M109 429Q82 429 66 447T50 491Q50 562 103 614T235 666Q326 666 387 610T449 465Q449 422 429 383T381 315T301 241Q265 210 201 149L142 93L218 92Q375 92 385 97Q392 99 409 186V189H449V186Q448 183 436 95T421 3V0H50V19V31Q50 38 56 46T86 81Q115 113 136 137Q145 147 170 174T204 211T233 244T261 278T284 308T305 340T320 369T333 401T340 431T343 464Q343 527 309 573T212 619Q179 619 154 602T119 569T109 550Q109 549 114 549Q132 549 151 535T170 489Q170 464 154 447T109 429Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-36&quot; d=&quot;M42 313Q42 476 123 571T303 666Q372 666 402 630T432 550Q432 525 418 510T379 495Q356 495 341 509T326 548Q326 592 373 601Q351 623 311 626Q240 626 194 566Q147 500 147 364L148 360Q153 366 156 373Q197 433 263 433H267Q313 433 348 414Q372 400 396 374T435 317Q456 268 456 210V192Q456 169 451 149Q440 90 387 34T253 -22Q225 -22 199 -14T143 16T92 75T56 172T42 313ZM257 397Q227 397 205 380T171 335T154 278T148 216Q148 133 160 97T198 39Q222 21 251 21Q302 21 329 59Q342 77 347 104T352 209Q352 289 347 316T329 361Q302 397 257 397Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-31&quot; d=&quot;M213 578L200 573Q186 568 160 563T102 556H83V602H102Q149 604 189 617T245 641T273 663Q275 666 285 666Q294 666 302 660V361L303 61Q310 54 315 52T339 48T401 46H427V0H416Q395 3 257 3Q121 3 100 0H88V46H114Q136 46 152 46T177 47T193 50T201 52T207 57T213 61V578Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-I-D7&quot; d=&quot;M630 29Q630 9 609 9Q604 9 587 25T493 118L389 222L284 117Q178 13 175 11Q171 9 168 9Q160 9 154 15T147 29Q147 36 161 51T255 146L359 250L255 354Q174 435 161 449T147 471Q147 480 153 485T168 490Q173 490 175 489Q178 487 284 383L389 278L493 382Q570 459 587 475T609 491Q630 491 630 471Q630 464 620 453T522 355L418 250L522 145Q606 61 618 48T630 29Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-2B&quot; d=&quot;M56 237T56 250T70 270H369V420L370 570Q380 583 389 583Q402 583 409 568V270H707Q722 262 722 250T707 230H409V-68Q401 -82 391 -82H389H387Q375 -82 369 -68V230H70Q56 237 56 250Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-3D&quot; d=&quot;M56 347Q56 360 70 367H707Q722 359 722 347Q722 336 708 328L390 327H72Q56 332 56 347ZM56 153Q56 168 72 173H708Q722 163 722 153Q722 140 707 133H70Q56 140 56 153Z&quot;&gt;&lt;/path&gt;&lt;path id=&quot;MJX-TEX-N-38&quot; d=&quot;M70 417T70 494T124 618T248 666Q319 666 374 624T429 515Q429 485 418 459T392 417T361 389T335 371T324 363L338 354Q352 344 366 334T382 323Q457 264 457 174Q457 95 399 37T249 -22Q159 -22 101 29T43 155Q43 263 172 335L154 348Q133 361 127 368Q70 417 70 494ZM286 386L292 390Q298 394 301 396T311 403T323 413T334 425T345 438T355 454T364 471T369 491T371 513Q371 556 342 586T275 624Q268 625 242 625Q201 625 165 599T128 534Q128 511 141 492T167 463T217 431Q224 426 228 424L286 386ZM250 21Q308 21 350 55T392 137Q392 154 387 169T375 194T353 216T330 234T301 253T274 270Q260 279 244 289T218 306L210 311Q204 311 181 294T133 239T107 157Q107 98 150 60T250 21Z&quot;&gt;&lt;/path&gt;&lt;/defs&gt;&lt;/svg&gt;&lt;/body&gt;&lt;/html&gt;
</content>
  </entry>
</feed>