<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" version="2.0">
  <channel>
    <title>Sufyan rambles</title>
    <link>https://dawoodjee.com/</link>
    <atom:link href="https://dawoodjee.com/feed.xml" rel="self" type="application/rss+xml"/>
    <description></description>
    <lastBuildDate>Tue, 01 Apr 2025 01:08:50 GMT</lastBuildDate>
    <language>en</language>
    <generator>Lume v2.0.3</generator>
    <item>
      <title>Pratt Parsing</title>
      <link>https://dawoodjee.com/blog/pratt-parsing/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/pratt-parsing/</guid>
      <description>The definitive guide</description>
      <content:encoded>
        <![CDATA[<p>Hundreds of Pratt parsing posts exist. I hope this one is relatively clear,
        concise, and comprehensive.</p>
        <p>Learn how Pratt parsers work by writing one. I assume familiarity with
        <a href="https://web.archive.org/web/20240413191133/https://web.mit.edu/6.102/www/sp24/classes/12-grammars-parsing/">grammars, parsers</a>
        and TypeScript.</p>
        <p>The
        <a href="https://github.com/eejdoowad/dawoodjee.com/blob/main/src/static/assets/pratt-parsing/parser.ts">code</a>
        is public domain. Send feedback by email.</p>
        <h3 id="motivation" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#motivation" class="header-anchor">Motivation</a></h3>
        <p>This is an intuitive expression grammar.</p>
        <pre><code>expr = expr (&quot;+&quot; | &quot;-&quot; | &quot;*&quot; | &quot;/&quot; | &quot;^&quot;) expr
        | &quot;-&quot; expr
        | number
        </code></pre>
        <p>The problem is it's ambiguous. <code>1 + 2 * 3</code> can be parsed as <code>(1 + 2) * 3</code> or
        <code>1 + (2 * 3)</code>.</p>
        <p>The textbook way to resolve ambiguity is to convolute the grammar with
        associativity and precedence rules.</p>
        <pre><code>expr = expr (&quot;+&quot; | &quot;-&quot;) term
        | term
        
        term = term (&quot;*&quot; | &quot;/&quot;) factor
        | factor
        
        factor = factor &quot;^&quot; number
        | &quot;-&quot; factor
        | number
        </code></pre>
        <p>This grammar is <em>not</em> intuitive. It gets worse if your parser cannot handle left
        recursion. And worse as you add more operators.</p>
        <p>The resulting syntax trees are cumbersome.</p>
        <h3 id="overview" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#overview" class="header-anchor">Overview</a></h3>
        <p>Pratt parsing uses grammars of this form.</p>
        <pre><code>expr = head tail*
        
        head = number
        | &quot;-&quot; expr
        
        tail = (&quot;+&quot; | &quot;-&quot; | &quot;*&quot; | &quot;/&quot; | &quot;^&quot;) expr
        </code></pre>
        <p>And produces syntax trees of this form.</p>
        <pre><code>expr = expr (&quot;+&quot; | &quot;-&quot; | &quot;*&quot; | &quot;/&quot; | &quot;^&quot;) expr
        | &quot;-&quot; expr
        | number
        </code></pre>
        <p>The parsing routine applies precedence and associativity rules to resolve
        grammar ambiguity.</p>
        <p>Pratt parsers are easy to understand, implement, and integrate.</p>
        <p>We'll build a series of gradually better parsers to explain how it works.</p>
        <h2 id="precedence-and-associativity" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#precedence-and-associativity" class="header-anchor">Precedence and Associativity</a></h2>
        <h3 id="left-associative-parser" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#left-associative-parser" class="header-anchor">Left-Associative Parser</a></h3>
        <p>Start simple. Assume all operators are left-associative and without precedence.</p>
        <p>Parse <code>1 + 2 + 3 * 4 * 5</code> as <code>((((1 + 2) + 3) * 4) * 5)</code>.</p>
        <p>The expression grammar supports this with a repeated tail that immediately
        applies the operator to expand the left expression.</p>
        <pre><code>expr = number (tail_op number)*
        |    | |               |
        └head┘ └──────tail─────┘
        </code></pre>
        <p>The implementation reflects the grammar.</p>
        <pre><code class="language-ts">function expr(ctx) {
        let left_expr = number(next_token(ctx));
        while (has_token(ctx)) {
        const op = tail_op(next_token(ctx));
        const right_expr = number(next_token(ctx));
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="right-associative-parser" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#right-associative-parser" class="header-anchor">Right-Associative Parser</a></h3>
        <p>Now assume all operators are right-associative and without precedence</p>
        <p>Parse <code>1 + 2 + 3 * 4 * 5</code> as <code>(1 + (2 + (3 * (4 * 5))))</code>.</p>
        <p>The expression grammar supports this with a right-recursive tail that expands
        the expression to the right before applying the operator.</p>
        <pre><code>expr = number (tail_op expr)?
        </code></pre>
        <p>The implementation reflects the grammar.</p>
        <pre><code class="language-ts">function expr(ctx) {
        let left_expr = number(next_token(ctx));
        if (has_token(ctx)) {
        const op = tail_op(next_token(ctx));
        const right_expr = expr(ctx);
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="mixed-associative-grammar" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#mixed-associative-grammar" class="header-anchor">Mixed-Associative Grammar</a></h3>
        <p>Sometimes the parser should apply an operator immediately as in the first
        parser. Other times it should wait until later operators are applied as in the
        second parser.</p>
        <p>Merge the grammars to create an ambiguous grammar that enables this choice:</p>
        <pre><code class="language-rs">expr = number (tail_op number)*   // Parser 1: left-associative
        expr = number (tail_op expr  )?   // Parser 2: right-associative
        expr = number (tail_op expr  )*   // Merged: mixed-associative
        </code></pre>
        <p>Take the second parser and rewrite <code>if</code> to <code>while</code>.</p>
        <pre><code class="language-ts">function expr(ctx) {
        let left_expr = number(next_token(ctx));
        while (has_token(ctx)) {
        const op = tail_op(next_token(ctx));
        const right_expr = expr(ctx);
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        </code></pre>
        <p>This parser resolves the grammar's ambiguity by always choosing to recurse,
        which makes it indistinguishable from the second parser.</p>
        <h3 id="respecting-associativity" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#respecting-associativity" class="header-anchor">Respecting Associativity</a></h3>
        <p>From experience we know right-associative operators should recurse.</p>
        <p>Left-associative operators should instead return the current expression to
        become part of the parent expression.</p>
        <pre><code class="language-ts">function expr(ctx, parent_op) {
        let left_expr = number(next_token(ctx));
        while (has_token(ctx)) {
        const op = tail_op(peek_token(ctx));
        if (assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        </code></pre>
        <p>Now the parser respects associativity but not precedence.</p>
        <h3 id="respecting-precedence" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#respecting-precedence" class="header-anchor">Respecting Precedence</a></h3>
        <p>Pratt parsing uses nested levels of recursion to represent nested expressions of
        equal or increasing precedence.</p>
        <p>By definition, a level exits when an operator with lower precedence appears.</p>
        <p>By definition, associativity applies when operators have equal precedence.</p>
        <p>The parent operator determines a level's precedence. The root expression has no
        parent, which means minimum precedence.</p>
        <pre><code class="language-ts">// Compares the precedence of two operators
        function cmp_precedence(op1, op2): &quot;&lt;&quot; | &quot;=&quot; | &quot;&gt;&quot;;
        
        function expr(ctx, parent_op) {
        let left_expr = number(next_token(ctx));
        while (has_token(ctx)) {
        const op = tail_op(peek_token(ctx));
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        
        expr(ctx, Op.Root); // Expected syntax for parsing an expression.
        </code></pre>
        <p>This parser respects precedence and associativity.</p>
        <h2 id="adding-operators" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#adding-operators" class="header-anchor">Adding Operators</a></h2>
        <h3 id="head-and-tail-parsing" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#head-and-tail-parsing" class="header-anchor">Head and Tail Parsing</a></h3>
        <p>To simplify future extensions, split the grammar into head and tail rules.</p>
        <pre><code>expr = expr_head expr_tail*
        
        expr_head = number
        
        expr_tail = tail_op expr
        </code></pre>
        <p>Refactor the parser accordingly.</p>
        <pre><code class="language-ts">function expr(ctx, parent_op) {
        const left_expr = expr_head(ctx);
        return expr_tail(ctx, parent_op, left_expr);
        }
        function expr_head(ctx) {
        return number(next_token(ctx));
        }
        function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const op = tail_op(peek_token(ctx));
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="prefix-operators" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#prefix-operators" class="header-anchor">Prefix Operators</a></h3>
        <p>Extend <code>expr_head</code> to support prefix operators.</p>
        <pre><code>expr_head = number
        | prefix_op expr
        </code></pre>
        <p>Operators like <code>-</code> can have overloaded infix and prefix definitions because
        <code>prefix_op</code> and <code>tail_op</code> don't conflict in the grammar.</p>
        <pre><code class="language-ts">function expr_head(ctx) {
        const token = next_token(ctx);
        if (is_number_token(token)) {
        return number(token);
        } else {
        const op = prefix_op(token);
        const right_expr = expr(ctx, op);
        return prefix(op, right_expr);
        }
        }
        </code></pre>
        <h3 id="postfix-operators" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#postfix-operators" class="header-anchor">Postfix Operators</a></h3>
        <p>Update <code>expr_tail</code> to support postfix operators.</p>
        <pre><code>expr_tail = postfix_op
        | infix_op expr
        </code></pre>
        <p>If an operator is both postfix and infix, the parser must look ahead to
        differentiate the two cases. Disallow this to simplify parsing.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const op = tail_op(peek_token(ctx));
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        if (is_postfix_op(op)) {
        left_expr = postfix(op, left_expr);
        } else {
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="parentheses" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#parentheses" class="header-anchor">Parentheses</a></h3>
        <p>Extend <code>expr_head</code> to support parenthesized expressions.</p>
        <pre><code>expr_head = &quot;(&quot; expr &quot;)&quot;
        | number
        | prefix_op expr
        </code></pre>
        <p>Parentheses prevent other operators from interacting with the enclosed
        expression, so precedence is reset.</p>
        <pre><code class="language-ts">function expr_head(ctx) {
        const token = next_token(ctx);
        if (token === &quot;(&quot;) {
        const right_expr = expr(ctx, Op.Root);
        next_token(ctx); // consume &quot;)&quot;
        return right_expr;
        } else if (is_number_token(token)) {
        return number(token);
        } else {
        const op = prefix_op(token);
        const right_expr = expr(ctx, op);
        return prefix(op, right_expr);
        }
        }
        </code></pre>
        <p><code>)</code> tokens appear where the parser otherwise expects tail operators. They mark
        the end of an expression.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const token = peek_token(ctx);
        if (token === &quot;)&quot;) break;
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        if (is_postfix_op(op)) {
        left_expr = postfix(op, left_expr);
        } else {
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="subscript-operator" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#subscript-operator" class="header-anchor">Subscript Operator</a></h3>
        <p>The (array) subscript operator <code>[</code> resembles a postfix operator because there is
        no right argument.</p>
        <p>Brackets prevent other operators from interacting with the enclosed expression.</p>
        <pre><code>expr_tail = infix_op expr
        | postfix_op
        | subscript_op
        
        subscript_op = &quot;[&quot; expr &quot;]&quot;
        </code></pre>
        <p>Like <code>)</code> tokens, <code>]</code> tokens mark the end of an expression.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const token = peek_token(ctx);
        if (token === &quot;)&quot; || token === &quot;]&quot;) break;
        const op = tail_op(token);
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        if (op === Op.Subscript) {
        const middle_expr = expr(ctx, Op.Root);
        next_token(ctx); // consume &quot;]&quot;
        left_expr = subscript(left_expr, middle_expr);
        } else if (is_postfix_op(op)) {
        left_expr = postfix(op, left_expr);
        } else {
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="ternary-operator" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#ternary-operator" class="header-anchor">Ternary Operator</a></h3>
        <p>The ternary operator <code>left ? middle : right</code> resembles an infix operator because
        it has an unenclosed right argument.</p>
        <p>The middle expression is enclosed so other operators cannot affect how it is
        parsed.</p>
        <pre><code>expr_tail = infix_op expr
        | postfix_op
        | subscript_op
        | ternary_op
        
        ternary_op = &quot;?&quot; expr &quot;:&quot; expr
        </code></pre>
        <p><code>:</code> tokens mark the end of an expression.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const token = peek_token(ctx);
        if (token === &quot;)&quot; || token === &quot;]&quot; || token === &quot;:&quot;) break;
        const op = tail_op(token);
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        next_token(ctx);
        if (op === Op.Ternary) {
        const middle_expr = expr(ctx, Op.Root);
        next_token(ctx); // consume &quot;:&quot;
        const right_expr = expr(ctx, op);
        left_expr = ternary(left_expr, middle_expr, right_expr);
        } else if (op === Op.Subscript) {
        const middle_expr = expr(ctx, Op.Root);
        next_token(ctx); // consume &quot;]&quot;
        left_expr = subscript(left_expr, middle_expr);
        } else if (is_postfix_op(op)) {
        left_expr = postfix(op, left_expr);
        } else {
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="ternary-operator-with-optional-else" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#ternary-operator-with-optional-else" class="header-anchor">Ternary Operator with Optional Else</a></h3>
        <p>Modify the ternary operator so <code>: right</code> is optional.</p>
        <p>Pair <code>:</code> to the closest unpaired <code>?</code> so that <code>a ? b ? c : d</code> parses as
        <code>a ? (b ? c : d)</code>.</p>
        <pre><code>ternary_op = &quot;?&quot; expr (&quot;:&quot; expr)?
        </code></pre>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        if (op === Op.Ternary) {
        const middle_expr = expr(ctx, Op.Root);
        let right_expr = null;
        if (peek_token(ctx) === &quot;:&quot;) {
        next_token(ctx); // consume &quot;:&quot;
        right_expr = expr(ctx, op);
        }
        left_expr = ternary(left_expr, middle_expr, right_expr);
        }
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <h2 id="non-associative-operators-and-relative-precedence" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#non-associative-operators-and-relative-precedence" class="header-anchor">Non-Associative Operators and Relative Precedence</a></h2>
        <h3 id="non-associative-operators" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#non-associative-operators" class="header-anchor">Non-Associative Operators</a></h3>
        <p>Is <code>&lt;&lt;</code> left-associative or right-associative?</p>
        <p>Since it's not obvious, forgo associativity and instead require disambiguating
        parentheses.</p>
        <p><code>a &lt;&lt; b</code> passes because it has one interpretation but <code>a &lt;&lt; b &lt;&lt; c</code> fails as
        ambiguous and requires parentheses to fix.</p>
        <pre><code class="language-ts">function error_assoc(op) {
        return new Error(
        `Operator &quot;${op}&quot; is not associative and requires parentheses`,
        );
        }
        
        function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot;) {
        if (assoc(op) === &quot;left&quot;) break;
        if (assoc(op) === &quot;none&quot;) throw error_assoc(op);
        }
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="relative-precedence" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#relative-precedence" class="header-anchor">Relative Precedence</a></h3>
        <p>Is <code>&lt;&lt;</code> or <code>**</code> higher precedence?</p>
        <p>Since global precedence is confusing, let's instead define relative precedence
        between operator pairs and require parentheses for unrelated operators.</p>
        <p>For example, if <code>Precedence(**) &gt; Precedence(/)</code> is the only known precedence
        relationship, then expect the following parse results:</p>
        <div class="table-div"><table>
        <thead>
        <tr>
        <th>Expression</th>
        <th>Parse Result</th>
        <th>Parse Trees</th>
        </tr>
        </thead>
        <tbody>
        <tr>
        <td><code>a &lt;&lt; b</code></td>
        <td>Ok</td>
        <td><code>(a &lt;&lt; b)</code></td>
        </tr>
        <tr>
        <td><code>a ** b / c</code></td>
        <td>Ok</td>
        <td><code>((a ** b) / c)</code></td>
        </tr>
        <tr>
        <td><code>a ** b &lt;&lt; c</code></td>
        <td>Ambiguous</td>
        <td><code>(a ** (b &lt;&lt; c))</code> and <code>((a ** b) &lt;&lt; c)</code></td>
        </tr>
        <tr>
        <td><code>a ** (b &lt;&lt; c)</code></td>
        <td>Ok</td>
        <td><code>(a + (b &lt;&lt; c))</code></td>
        </tr>
        </tbody>
        </table>
        </div><p>The parser's <code>cmp_precedence()</code> now returns <code>&quot;!&quot;</code> for unrelated operators.</p>
        <pre><code class="language-ts">function error_unrelated_ops(op1, op2) {
        return new Error(`&quot;${op1}&quot; is unrelated to &quot;${op2}&quot;`);
        }
        
        function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;!&quot;) throw error_unrelated_ops(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot;) {
        if (assoc(op) === &quot;left&quot;) break;
        if (assoc(op) === &quot;none&quot;) throw error_assoc(op);
        }
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="precedence-groups" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#precedence-groups" class="header-anchor">Precedence Groups</a></h3>
        <p>Defining the relative precedence between every pair of operators is unscalable
        in languages with many operators.</p>
        <p>One solution is to organize operators into groups that share the same
        associativity and relative precedence.</p>
        <p>Let's define a variety of operators, group them, and define each group's
        relative precedence.</p>
        <div class="table-div"><table>
        <thead>
        <tr>
        <th>Precedence Group</th>
        <th>Operators</th>
        <th>Associativity</th>
        <th>Greater Precedence Than</th>
        </tr>
        </thead>
        <tbody>
        <tr>
        <td>Postfix</td>
        <td>++ -- [</td>
        <td>none</td>
        <td>Prefix</td>
        </tr>
        <tr>
        <td>Prefix</td>
        <td>++ -- + - !</td>
        <td>none</td>
        <td>BitwiseShift, Exponentiation</td>
        </tr>
        <tr>
        <td>BitwiseShift</td>
        <td>&lt;&lt; &gt;&gt;</td>
        <td>none</td>
        <td>Comparison</td>
        </tr>
        <tr>
        <td>Exponentiation</td>
        <td>**</td>
        <td>right</td>
        <td>Multiplication</td>
        </tr>
        <tr>
        <td>Multiplication</td>
        <td>* / &amp; %</td>
        <td>left</td>
        <td>Addition</td>
        </tr>
        <tr>
        <td>Addition</td>
        <td>+ - |</td>
        <td>left</td>
        <td>Comparison</td>
        </tr>
        <tr>
        <td>Comparison</td>
        <td>== != &lt; &lt;= &gt; &gt;=</td>
        <td>none</td>
        <td>Conjunction</td>
        </tr>
        <tr>
        <td>Conjunction</td>
        <td>&amp;&amp;</td>
        <td>left</td>
        <td>Disjunction</td>
        </tr>
        <tr>
        <td>Disjunction</td>
        <td>||</td>
        <td>left</td>
        <td>Ternary</td>
        </tr>
        <tr>
        <td>Ternary</td>
        <td>?</td>
        <td>right</td>
        <td>Root</td>
        </tr>
        <tr>
        <td>Root</td>
        <td></td>
        <td>none</td>
        <td></td>
        </tr>
        </tbody>
        </table>
        </div><p>Given precedence <code>P</code>, <code>P(Multiplication) &gt; P(Comparison)</code> because precedence is
        transitive: <code>P(A) &gt; P(B)</code> and <code>P(B) &gt; P(C)</code> imply <code>P(A) &gt; P(C)</code>.</p>
        <p>Precedence groups form a digraph. Note how BitwiseShift is unrelated to
        Exponentiation, Multiplication, and Addition.</p>
        <h3 id="implementation" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#implementation" class="header-anchor">Implementation</a></h3>
        <p>Encode the precedence table in TypeScript.</p>
        <pre><code class="language-ts">const enum Op {
        PostfixPlusPlus,
        PostfixMinusMinus,
        Subscript,
        // ... remaining operators
        }
        const enum Group {
        Postfix,
        Prefix,
        BitwiseShift,
        // ... remaining groups
        }
        const groups = [
        {
        id: Group.Postfix,
        ops: [Op.PostfixPlusPlus, Op.PostfixMinusMinus /* , ... */],
        assoc: &quot;none&quot;,
        gt: [Group.Prefix],
        },
        {
        id: Group.Prefix,
        ops: [Op.PrefixPlusPlus, Op.PrefixMinusMinus /* , ... */],
        assoc: &quot;none&quot;,
        gt: [Group.BitwiseShift, Group.Exponentiation],
        },
        {
        id: Group.BitwiseShift,
        ops: [Op.BitwiseShiftLeft, Op.BitwiseShiftRight],
        assoc: &quot;none&quot;,
        gt: [Group.Comparison],
        },
        // ... remaining groups
        ];
        </code></pre>
        <p>Precompute mappings from operators to groups and associativities.</p>
        <pre><code class="language-ts">const map_op_to_group = new Map&lt;Op, Group&gt;();
        const map_op_to_assoc = new Map&lt;Op, &quot;left&quot; | &quot;right&quot; | &quot;none&quot;&gt;();
        
        for (const group of groups) {
        for (const op of group.ops) {
        map_op_to_group.set(op, group.id);
        map_op_to_assoc.set(op, group.assoc);
        }
        }
        </code></pre>
        <p><code>assoc()</code> is a map lookup.</p>
        <pre><code class="language-ts">function assoc(op) {
        return map_op_to_assoc.get(op)!;
        }
        </code></pre>
        <p>For each precedence group, precompute lower precedence groups using depth-first
        search.</p>
        <pre><code class="language-ts">const precedence_gt = new Map&lt;Group, Set&lt;Group&gt;&gt;();
        
        for (const src of groups) precedence_gt.set(src.id, new Set(src.gt));
        for (const [_, dsts] of precedence_gt) {
        const explore = [...dsts];
        while (explore.length) {
        const dst = explore.pop()!;
        dsts.add(dst);
        const next_dsts = precedence_gt.get(dst)!;
        for (const new_dst of next_dsts) {
        if (!dsts.has(new_dst)) {
        explore.push(new_dst);
        }
        }
        }
        }
        </code></pre>
        <p><code>cmp_precedence()</code> refers to the map.</p>
        <pre><code class="language-ts">function cmp_precedence(op1, op2) {
        const group1 = map_op_to_group.get(op1)!;
        const group2 = map_op_to_group.get(op2)!;
        if (group1 === group2) return &quot;=&quot;;
        if (precedence_gt.get(group1)!.has(group2)) return &quot;&gt;&quot;;
        if (precedence_gt.get(group2)!.has(group1)) return &quot;&lt;&quot;;
        return &quot;!&quot;;
        }
        </code></pre>
        <h2 id="wrap-up" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#wrap-up" class="header-anchor">Wrap Up</a></h2>
        <h3 id="error-handling" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#error-handling" class="header-anchor">Error Handling</a></h3>
        <p>Handle errors by checking for unexpected tokens.</p>
        <p>Start with the head parser.</p>
        <pre><code class="language-ts">function error_bad_token(token) {
        return new Error(`Bad token &quot;${token}&quot;`);
        }
        
        function expr_head(ctx) {
        const token = next_token(ctx);
        if (token === &quot;(&quot;) {
        const right_expr = expr(ctx, Op.Root);
        const rparen = next_token(ctx);
        if (rparen !== &quot;)&quot;) throw error_bad_token(rparen, &quot;)&quot;);
        return right_expr;
        } else if (is_number_token(token)) {
        return number(token);
        } else if (is_prefix_op_token(token)) {
        const op = prefix_op(token);
        const right_expr = expr(ctx, op);
        return prefix(op, right_expr);
        } else {
        throw error_bad_token(token ?? &quot;EOF&quot;);
        }
        }
        </code></pre>
        <p>And now the tail parser.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        const token = peek_token(ctx);
        if (token === &quot;)&quot; || token === &quot;]&quot; || token === &quot;:&quot;) break;
        const op = tail_op(token);
        if (op === undefined) throw error_bad_token(token);
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;!&quot;) throw error_unrelated_ops(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot;) {
        if (assoc(op) === &quot;left&quot;) break;
        if (assoc(op) === &quot;none&quot;) throw error_assoc(op);
        }
        next_token(ctx);
        if (op === Op.Ternary) {
        const middle_expr = expr(ctx, Op.Root);
        let right_expr = undefined;
        if (peek_token(ctx) === &quot;:&quot;) {
        next_token(ctx);
        right_expr = expr(ctx, op);
        }
        left_expr = ternary(left_expr, middle_expr, right_expr);
        } else if (op === Op.Subscript) {
        const middle_expr = expr(ctx, Op.Root);
        const rbracket = next_token(ctx);
        if (rbracket !== &quot;]&quot;) throw error_bad_token(rbracket);
        left_expr = subscript(left_expr, middle_expr);
        } else if (is_postfix_op(op)) {
        left_expr = postfix(op, left_expr);
        } else if (is_infix_op(op)) {
        const right_expr = expr(ctx, op);
        left_expr = infix(op, left_expr, right_expr);
        }
        }
        return left_expr;
        }
        </code></pre>
        <h3 id="grammar" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#grammar" class="header-anchor">Grammar</a></h3>
        <p>This is the final grammar.</p>
        <pre><code>expr = expr_head expr_tail*
        
        expr_head = number
        | prefix_op expr
        
        expr_tail = infix_op expr
        | postfix_op
        | &quot;[&quot; expr &quot;]&quot;
        | &quot;?&quot; expr (&quot;:&quot; expr)?
        </code></pre>
        <p>The parser produces syntax trees of this form.</p>
        <pre><code>expr = number
        | prefix
        | postfix
        | infix
        | ternary
        | subscript
        
        prefix = prefix_op expr
        postfix = expr postfix_op
        infix = expr infix_op expr
        ternary = expr &quot;?&quot; expr (&quot;:&quot; expr)?
        subscript = expr &quot;[&quot; expr &quot;]&quot;
        </code></pre>
        <h3 id="parser" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#parser" class="header-anchor">Parser</a></h3>
        <p>Code for the final parser is
        <a href="https://github.com/eejdoowad/dawoodjee.com/blob/main/src/static/assets/pratt-parsing/parser.ts">here</a>
        It includes a scanner and makes greater use of types and enums.</p>
        <p>Alternative code that uses an explicit stack (instead of the call stack) is
        <a href="https://github.com/eejdoowad/dawoodjee.com/blob/main/src/static/assets/pratt-parsing/stack_parser.ts">here</a>.</p>
        <p>Run tests with <code>deno test parser.ts</code>.</p>
        <h3 id="binding-power" tabindex="-1"><a href="https://dawoodjee.com/blog/pratt-parsing/#binding-power" class="header-anchor">Binding Power</a></h3>
        <p>Other posts explain Pratt parsing using &quot;binding power.&quot;</p>
        <p>Each operator is assigned a left and right binding power based on its precedence
        and associativity:</p>
        <div class="table-div"><table>
        <thead>
        <tr>
        <th>Operator</th>
        <th>Left Binding Power</th>
        <th>Right Binding Power</th>
        <th>Notes</th>
        </tr>
        </thead>
        <tbody>
        <tr>
        <td>**</td>
        <td>6</td>
        <td>5</td>
        <td>Right-associative operators have higher left binding power</td>
        </tr>
        <tr>
        <td>*</td>
        <td>3</td>
        <td>4</td>
        <td>Left-associative operators have higher right binding power</td>
        </tr>
        <tr>
        <td>+</td>
        <td>1</td>
        <td>2</td>
        <td>Operators with lower precedence have lower binding powers</td>
        </tr>
        <tr>
        <td>-</td>
        <td>1</td>
        <td>2</td>
        <td>Operators with the same precedence and associativity have the same binding powers</td>
        </tr>
        </tbody>
        </table>
        </div><p>The condition for exiting a level is simplified to a binding power comparison.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        if (binding_power_right(op) &lt; binding_power_left(parent_op)) break;
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <p>I think binding power is less intuitive than the equivalent precedence and
        associativity checks.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot; &amp;&amp; assoc(op) === &quot;left&quot;) break;
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <p>I think requiring parentheses on expressions that are ambiguous <em>to humans</em> is
        good language design.</p>
        <pre><code class="language-ts">function expr_tail(ctx, parent_op, left_expr) {
        while (has_token(ctx)) {
        // ...
        const order = cmp_precedence(op, parent_op);
        if (order === &quot;!&quot;) throw error_unrelated_ops(op, parent_op);
        if (order === &quot;&lt;&quot;) break;
        if (order === &quot;=&quot;) {
        if (assoc(op) === &quot;left&quot;) break;
        if (assoc(op) === &quot;none&quot;) throw error_assoc(op);
        }
        // ...
        }
        return left_expr;
        }
        </code></pre>
        <p>But binding power requires operators to have global precedence and
        associativity.</p>
        ]]>
      </content:encoded>
      <pubDate>Sat, 14 Dec 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Hummus Recipe</title>
      <link>https://dawoodjee.com/blog/hummus-recipe/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/hummus-recipe/</guid>
      <description>Simple but Good</description>
      <content:encoded>
        <![CDATA[<h2 id="ingredients" tabindex="-1"><a href="https://dawoodjee.com/blog/hummus-recipe/#ingredients" class="header-anchor">Ingredients</a></h2>
        <ul>
        <li>Canned chickpeas, 1 can/15oz/425g</li>
        <li>Tahini, 1/3 cup</li>
        <li>Olive oil, 2 tablespoons</li>
        <li>Lemon, 1</li>
        <li>Garlic, 2 cloves</li>
        <li>Cumin, 1/2 teaspoon</li>
        <li>Salt, 3/4 teaspoon</li>
        <li>Ice or cold water</li>
        <li>Sumac</li>
        </ul>
        <h2 id="steps" tabindex="-1"><a href="https://dawoodjee.com/blog/hummus-recipe/#steps" class="header-anchor">Steps</a></h2>
        <ol>
        <li>Drain and rinse chickpeas</li>
        <li>Add tahini, olive oil, lemon juice, crushed garlic, cumin, and salt to food
        processor</li>
        <li>Blend until smooth</li>
        <li>Add half cup chicpeas and blend until smooth</li>
        <li>Add remaining chickpeas and blend until smooth</li>
        <li>Add ice or cold water until desired consistency</li>
        <li>Serve in wide container, drizzle olive oil, and sprinkle sumac</li>
        </ol>
        ]]>
      </content:encoded>
      <pubDate>Sat, 19 Oct 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Postgres timestamp vs timestamptz</title>
      <link>https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/</guid>
      <description>An overview because I can never remember the difference</description>
      <content:encoded>
        <![CDATA[<h2 id="recommendations" tabindex="-1"><a href="https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/#recommendations" class="header-anchor">Recommendations</a></h2>
        <ul>
        <li>Always use <code>timestamptz</code>.</li>
        <li>Always set the timezone to UTC using <code>timezone = 'UTC'</code> in <code>postgresq.conf</code>.</li>
        <li>Always specify the timezone when updating tables or working with timestamp
        literals.</li>
        </ul>
        <h2 id="definitions" tabindex="-1"><a href="https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/#definitions" class="header-anchor">Definitions</a></h2>
        <p><code>timestamptz</code> represents a specific instance in universal time. It can be used
        to represent when an account is created, when a user posts a comment, or when a
        meeting will start. <code>timestamptz</code> is <em>not</em> the combination of a timestamp and a
        time zone. It is impossible to recover the time zone used to create a
        <code>timestamptz</code> value.</p>
        <p><code>timestamp</code> represents a conceptual time that might vary based on the frame of
        reference. For example, New Year's Day starts on Janaury 1 at midnight, but
        midnight occurs at different times across planet Earth. <code>timestamp</code> is <em>not</em> a
        timestamp relative to the UTC time zone; although the underlying storage format
        and existing misuse may imply as such.</p>
        <p><code>reality</code> is chaos. If you work on a system with different interpretations of
        <code>timestamp</code> and <code>timestamptz</code>, embracing the chaos may help preserve sanity.</p>
        <h2 id="literals" tabindex="-1"><a href="https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/#literals" class="header-anchor">Literals</a></h2>
        <p>Timestamps can be created using the ISO 8601 text format.</p>
        <div class="table-div"><table>
        <thead>
        <tr>
        <th>type</th>
        <th>literal syntax</th>
        </tr>
        </thead>
        <tbody>
        <tr>
        <td><code>timestamptz</code></td>
        <td><code>timestamptz '2004-10-19 10:23:54-07'</code></td>
        </tr>
        <tr>
        <td><code>timestamp</code></td>
        <td><code>timestamp '2004-10-19 10:23:54'</code></td>
        </tr>
        </tbody>
        </table>
        </div><p>The optional time zone suffix at the end of a literal indicates the offset
        relative to UTC, e.g. UTC is UTC+00, PST is UTC-07, and IST is UTC+05:30</p>
        <p>If a <code>timestamp</code> is created from a literal with a time zone suffix, the suffix
        will be silently ignored.</p>
        <pre><code class="language-sql">select timestamp '2004-10-19 06:23:54+02';
        select timestamp '2004-10-19 06:23:54-07';
        -- Both queries return 2004-10-19 06:23:54
        </code></pre>
        <p>If a <code>timestamptz</code> is created from a literal without a time zone suffix, the
        time zone is assumed to be the session time zone.</p>
        <pre><code class="language-sql">set timezone = 'America/Los_Angeles';
        select timestamptz '2004-10-19 06:23:54';
        -- Returns 2004-10-19 06:23:54-07
        
        set timezone = 'America/New_York';
        select timestamptz '2004-10-19 06:23:54';
        -- Returns 2004-10-19 06:23:54-04
        </code></pre>
        <h2 id="storage" tabindex="-1"><a href="https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/#storage" class="header-anchor">Storage</a></h2>
        <p>Both timestamp and timestamptz are stored as 64-bit integers representing the
        offset in microseconds since 2000-01-01 00:00:00 UTC.</p>
        <pre><code class="language-sql">select extract(epoch from timestamp '2004-10-19 10:23:54');
        select extract(epoch from timestamptz '2004-10-19 06:23:54-04');
        select extract(epoch from timestamptz '2004-10-19 03:23:54-07');
        -- All queries return 1098181434
        
        select extract(epoch from timestamp '2004-10-19 10:23:54')
        - extract(epoch from timestamp '2000-01-01 00:00:00');
        -- Returns 151496634
        
        -- 151496634 seconds = 151496634000000 microseconds = 0x000089C90F0DE280 microseconds
        -- If you go digging, you'll find 64-bit integer 0x000089C90F0DE280 in memory.
        </code></pre>
        <p>Even though <code>timestamp</code> values are stored as offsets relative to the dawn of the
        millennium in UTC time, they should not be interpreted as timestamps in the UTC
        time zone. Instead, refer to the prior definition of <code>timestamp</code>.</p>
        <p>Note that <code>timestamptz</code> does <em>not</em> store the time zone. The time zone used to
        create a <code>timestamptz</code> value cannot be recovered.</p>
        <h2 id="serialization" tabindex="-1"><a href="https://dawoodjee.com/blog/postgres-timestamp-vs-timestamptz/#serialization" class="header-anchor">Serialization</a></h2>
        <p><code>timestamptz</code> values are serialized using the session time zone.</p>
        <pre><code class="language-sql">set timezone = 'America/Los_Angeles';
        select timestamptz '2004-10-19 06:23:54-03';
        -- Returns 2004-10-19 02:23:54-07
        
        set timezone = 'America/New_York';
        select timestamptz '2004-10-19 06:23:54-03';
        -- Returns 2004-10-19 05:23:54-04
        </code></pre>
        <p>Serialization of <code>timestamp</code> values is time zone independent. The returned value
        mirrors the source literal.</p>
        <pre><code class="language-sql">set timezone = 'America/Los_Angeles';
        select timestamp '2004-10-19 06:23:54';
        -- Returns 2004-10-19 06:23:54
        
        set timezone = 'America/New_York';
        select timestamp '2004-10-19 06:23:54';
        -- Returns 2004-10-19 06:23:54
        </code></pre>
        ]]>
      </content:encoded>
      <pubDate>Mon, 06 May 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Kumquat Marmalade</title>
      <link>https://dawoodjee.com/blog/kumquat-marmalade-recipe/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/kumquat-marmalade-recipe/</guid>
      <description>Yummy with toast and butter</description>
      <content:encoded>
        <![CDATA[<p><img src="https://dawoodjee.com/assets/kumquat-marmalade-recipe/jars-of-marmalade.avif#small" alt="Jars of marmalade"></p>
        <h2 id="ingredients" tabindex="-1"><a href="https://dawoodjee.com/blog/kumquat-marmalade-recipe/#ingredients" class="header-anchor">Ingredients</a></h2>
        <ul>
        <li>Fresh kumquats, ~500 grams</li>
        <li>Fresh lemon, 1</li>
        <li>Cane Sugar, 3/4 weight of kumquats</li>
        <li>Ginger (optional), 1 teaspoon</li>
        <li>Sanitized Jars</li>
        </ul>
        <h2 id="steps" tabindex="-1"><a href="https://dawoodjee.com/blog/kumquat-marmalade-recipe/#steps" class="header-anchor">Steps</a></h2>
        <ol>
        <li>Wash kumquats and remove blemishes</li>
        <li>Slice kumquats into ~3-5mm circular cross sections into dish</li>
        <li>Zest lemon into dish</li>
        <li>Extract lemon pulp into chunks and remove rind, then add to dish</li>
        <li>Weigh mixture, then add 3/4 as much sugar by weight</li>
        <li>Mix sugar into mixture</li>
        <li>Macerate mixture by letting it sit at room temperature for a couple hours or
        overnight in the fridge</li>
        <li>Place mixture into pot</li>
        <li>(Optionally) grate 1 teaspoon ginger into mixture</li>
        <li>Boil mixture on medium heat</li>
        <li>Periodically mix with silicon spatula</li>
        <li>Take it out when it's ready (watch a video)</li>
        <li>Pour hot mixture into sanitized jars and seal</li>
        <li>Allow jars to sit until cooled to room temperature</li>
        <li>Refrigerate and eat</li>
        </ol>
        <h2 id="notes" tabindex="-1"><a href="https://dawoodjee.com/blog/kumquat-marmalade-recipe/#notes" class="header-anchor">Notes</a></h2>
        <ul>
        <li>All quantities are guesstimates</li>
        <li>1-to-1 is the standard mixture to sugar ratio, but we like it less sweet</li>
        <li>Granulated sugar also works, but we prefer cane sugar</li>
        <li>Make sure to sanitize jars before use</li>
        <li>8 oz mason jars are the right size</li>
        <li>In the future, we'll experiment with spices like saffron, cardamom, star
        anise, and cinnamon</li>
        </ul>
        ]]>
      </content:encoded>
      <pubDate>Sun, 10 Mar 2024 00:00:00 GMT</pubDate>
    </item>
    <item>
      <title>Focused Search Experience</title>
      <link>https://dawoodjee.com/blog/focused-search-experience/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/focused-search-experience/</guid>
      <description>How and why I remove distractions from my search experience</description>
      <content:encoded>
        <![CDATA[<p>Focus is precious. It's a necessary ingredient of usefully invested time, but
        comes in short supply and wastes easily, which means it should be guarded and
        used strategically.</p>
        <p>Distraction is a major cause of waste. I've taken measures to fight distractions
        in my search experience because my useful work involves lots of searching.</p>
        <h2 id="overview" tabindex="-1"><a href="https://dawoodjee.com/blog/focused-search-experience/#overview" class="header-anchor">Overview</a></h2>
        <p>This is my search experience:</p>
        <p><video controls src="https://dawoodjee.com/assets/focused-search-experience/preview.webm" ></video></p>
        <p>And this is how it works:</p>
        <ul>
        <li><a href="https://programmablesearchengine.google.com/">Google Programmable Search Engine</a>
        <ul>
        <li>Search results only includes links and not multimedia or suggestions</li>
        <li>Custom filters remove distracting and low-value websites from search results</li>
        </ul>
        </li>
        <li><a href="https://ublockorigin.com/">uBlock Origin</a>
        <ul>
        <li>Blocks search ads</li>
        <li>Custom filter rules hide UI elements that aren't useful to me</li>
        <li>Custom filter rules restyle UI elements for better usability</li>
        </ul>
        </li>
        <li><a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a>
        <ul>
        <li>Toolbar customized to hide distracting UI elements</li>
        <li>Searchbar customized to disable all suggestions</li>
        <li>Search engine configured to use my custom search engine by default</li>
        </ul>
        </li>
        </ul>
        <h2 id="google-programmable-search-engine" tabindex="-1"><a href="https://dawoodjee.com/blog/focused-search-experience/#google-programmable-search-engine" class="header-anchor">Google Programmable Search Engine</a></h2>
        <p>I'm surprised Google wants anyone to user a search engine that's not google.com,
        but Programmable Search Engine exists, somehow. It competes with the likes of
        Algolia by offering customized website search as a service, but it also works as
        a standalone search tool.</p>
        <p>This is how it compares to google.com:</p>
        <p>Pros</p>
        <ul>
        <li>It's free, so there's no need to maintain a subscription like with
        <a href="https://kagi.com/">Kagi</a></li>
        <li>Search results are instant</li>
        <li>Search results are relevant</li>
        <li>It supports filtering custom domains from search results</li>
        <li>It doesn't have distracting anti-features like multi-media results, related
        searches, favicons, embedded YouTube player</li>
        </ul>
        <p>Cons</p>
        <ul>
        <li>More frequent anti-bot protections, which require manual interaction to bypass</li>
        <li>No support for time range filters, e.g. only show results from past month</li>
        <li>Lack of useful built-in tools like: calculator, dictionary, weather forecast,
        unit conversions, time and date tools, etc.</li>
        <li>No support for personalized search results, but I don't mind since
        personalized results usually reinforce my bad habits.</li>
        </ul>
        <p><a href="https://cse.google.com/cse?cx=b08029aadeb444a97">My custom search engine</a>
        configuration filters out websites that make life worse for various reasons.</p>
        <ul>
        <li>Some websites are too engaging, leading to addiction and doom scrolling. Since
        we can't have a healthy relationship, it's better to eliminate temptation by
        removing them from notice.</li>
        <li>Some websites have mostly low quality content, so you're better off looking
        elsewhere. Sometimes websites are SEO content farms. Other times content is
        user generated, and the low barrier to entry leads to &quot;the blind leading the
        blind.&quot; I wish there was a way to block all domains in the Medium syndicate.</li>
        <li>Some websites are walled gardens. I'd rather not create an account and
        sacrifice my privacy to access them.</li>
        <li>Some websites use intrusive dark patterns that render their content not worth
        the painful user experience.</li>
        </ul>
        <p>This is my current blocklist:</p>
        <pre><code>*.itnext.io/*
        *.dev.to/*
        *.tiktok.com/*
        *.quora.com/*
        *.w3schools.com/*
        *.facebook.com/*
        *.pinterest.com/*
        *.instagram.com/*
        *.medium.com/*
        *.linkedin.com/*
        *.twitter.com/*
        *.ycombinator.com/*
        *.reddit.com/*
        </code></pre>
        <p>I also updated my custom search engine to return 20 results per page instead of
        the default 10 to reduce the need to click the next page button.</p>
        <h2 id="ublock-origin" tabindex="-1"><a href="https://dawoodjee.com/blog/focused-search-experience/#ublock-origin" class="header-anchor">uBlock Origin</a></h2>
        <p>The user experience of Google Programmable Search Engine is terrible because ads
        use up the entire viewport on the results page, forcing users to scroll down to
        see any results.</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/ads.avif" alt="A search shows nothing but ads"></p>
        <p>Examples like this show how the quest for monetization has rendered the web
        unusable. Ad blockers exist to defend ourselves from this malpractice.</p>
        <p>uBlock Origin is <em>the</em> content blocking web extension. It blocks ads on the
        search results page by default. This is the result.</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/no-ads.avif" alt="A search with uBlock Origin shows results, not ads"></p>
        <p>Much better! But it can be refined further:</p>
        <ul>
        <li>The blue search button is useless to me because I always submit my searches
        using the &quot;Enter&quot; key.</li>
        <li>The &quot;x&quot; button is useless to me because I never clear the search query.</li>
        <li>The &quot;About X results (Y seconds)&quot; notice is useless to me. I don't care unless
        something went wrong.</li>
        <li>The &quot;Sort by relevance/recency&quot; toggle is useless because the recency sort is
        useless. What I really want is a time range filter.</li>
        <li>There's additional Google branding at the bottom of the page I don't care
        about.</li>
        <li>Because results are aligned to the left side of the viewport, I have to turn
        my head or eyes to see them. Center alignment is more ergonomic.</li>
        </ul>
        <p>This is the result:</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/preview.avif" alt="Search UX without distractions"></p>
        <p>And these are the underlying uBlock Origin filters:</p>
        <pre><code>cse.google.com##body:style(width: 580px; margin: auto)
        cse.google.com###cse-search-form:style(width: 580px !important; padding: 0 12px !important)
        cse.google.com##.gsib_b,.gsc-search-button,.gsc-above-wrapper-area
        cse.google.com##.gsc-adBlock,#cse-footer,.gcsc-find-more-on-google-branding
        </code></pre>
        <p>Unfortunately, there's no way to get around the reCAPTCHA.</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/recaptcha.avif" alt="I hate reCAPTCHA"></p>
        <h2 id="firefox" tabindex="-1"><a href="https://dawoodjee.com/blog/focused-search-experience/#firefox" class="header-anchor">Firefox</a></h2>
        <p>Almost all of my searches begin in Firefox, which I use because it best supports
        uBlock Origin
        <a href="https://github.com/gorhill/uBlock?tab=readme-ov-file#firefox">today</a> and in the
        <a href="https://github.com/uBlockOrigin/uBlock-issues/issues/338">long term</a>.</p>
        <p>I start by typing <code>cmd + L</code> to select Firefox's search bar and begin typing my
        query. Helpful suggestions appear to save me keystrokes and send me where I want
        to go.</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/suggestions.avif#small" alt="Search dialog with lots of annoying suggestions"></p>
        <p>I lied. The default suggestions UI is terrible for the usual reason,
        monetization. I actually don't want any suggestions.</p>
        <ul>
        <li>History suggestions are likely to reinforce bad habits as they guide me back
        toward irresistible, mindless entertainment.</li>
        <li>On the infrequent occasion when I need it, dedicated bookmark search works
        great.</li>
        <li>Searching for tabs means I have too many tabs open and can't keep track of
        them.</li>
        <li>Search engine completions are likely to drive me on a tangent, so I'd rather
        do the work to type the query myself.</li>
        <li>Why are there colorful pictures in my search suggestions? I'm not here for
        them, but I can't not look.</li>
        <li>How could I not want shopping suggestions from online retailers???</li>
        </ul>
        <p>Firefox makes it somewhat tedious, but I was able to disable all suggestions. In
        the process, I also set the default search engine to my custom search engine and
        switched to a light browser theme.</p>
        <p><img src="https://dawoodjee.com/assets/focused-search-experience/no-suggestions.avif#small" alt="Search dialog with lots of annoying suggestions"></p>
        <h2 id="what's-next%3F" tabindex="-1"><a href="https://dawoodjee.com/blog/focused-search-experience/#what's-next%3F" class="header-anchor">What's next?</a></h2>
        <p>Mission accomplished. I created a distraction-free search experience.</p>
        <p>But it's sad to think about the lost potential of people without the ability or
        determination to take similar measures. While a few people might come across
        this post and learn a trick or two to better their search experience, I think it
        would be far more valuable to the world if there were a more accessible way to
        apply anti-distraction measures. I shared some thoughts on this in
        <a href="https://dawoodjee.com/blog/web-agency">my Web Agency post</a>.</p>
        <p>If I could wave a magic wand to change one thing in my setup, it would be for
        Safari to support the web extension APIs needed by uBlock Origin. I like the
        minimalism of desktop Safari's controversial compact-mode UI, which embeds the
        search bar in the active tab widget and themes the toolbar to blend in with the
        current website.</p>
        ]]>
      </content:encoded>
      <pubDate>Thu, 07 Mar 2024 00:00:00 GMT</pubDate>
      <atom:updated>2024-03-10T00:00:00.000Z</atom:updated>
    </item>
    <item>
      <title>Web Agency</title>
      <link>https://dawoodjee.com/blog/web-agency/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/web-agency/</guid>
      <description>Thoughts on giving people agency over their web browsing experience</description>
      <content:encoded>
        <![CDATA[<p>Reader mode is a fantastic browser feature because it standardizes how content
        is displayed across disparate websites, removing distractions and the need to
        learn each website's bespokoe interface. RSS readers take this concept to its
        extreme by automatically providing a standardized feed of content, removing the
        need to navigate custom UIs to find content. Which is great when you know
        before-hand what content you care about, but doesn't address the need for
        content-on-demand. If you're listening to a song and find yourself interested in
        the history behind it, then you'll need to search for it and navigate the web to
        find it. It's the year 2024, and you might be able to use an LLM to find the
        answer, skipping the whole web maze, but at least for now LLMs I use don't
        provide the context or level of trust I want.</p>
        <p>So you go through this web maze and each page provides its own unique experience
        usually optimized for keeping you in the network or showing you ads. You've
        trained yourself to ignore the extraneous, intrusive information and manage to
        find the information you want. Or maybe you didn't. Perhaps you clicked on an ad
        interleaved with the content you actually care about (e.g. on Google Search or
        Reddit) or perhaps you saw a suggestion for a relation question or topic you
        might be interested in and followed it, or perhaps a video thumbnail caught your
        attention, and before you know it, you forget the original intent of your
        search.</p>
        <p>Digressing from the negative effects of platform incentives, you might find
        yourself on a website that is optimized for conveying the information you want.
        But you still have to understand its UI and the presentation might slow you
        down; perhaps the text is too small, the font is illegible, the sidebars push
        in-network content, there's a like count at the top, there are low-value user
        comments, there's a waste-of-(mental)-space stock image at the top... you get
        the point. Reader mode is supposed to eliminate these details. But reader mode
        doesn't work everywhere, it removes features that help you navigate and interact
        in useful ways, its heuristic algorithm sometimes removes content you care
        about, and you have to manually enable (and disable) it.</p>
        <p>What we really need is a way to standardize the experience of each website to
        our preference, with ways to remove specific UI elements we don't care about,
        standardize presentational details like fonts and page alignment, and
        configurable presets so we don't have to define everything ourselves. Basically,
        we need a user-agent that gives me agency over the web in a nice usable package.
        For example, I hate profile images because they steal focus use up valuable
        space, so I want them all gone across all websites. I want my browser to have a
        &quot;turn off profile images across all websites&quot; checkbox. This functionality
        doesn't exist because every website is different and there is no universal way
        to identify profile images. We actually need to hardcode this logic for every
        website, which appears infeasible. But is it really?</p>
        <p>The 80/20 rule applies but in this case I think it's really a 99.9/0.01 rule. We
        don't need to hardcode logic for every website on the internet, we just need
        rules for the tiny minority of websites that account for the majority of use. I
        prefer not to see profile images at all, but if they're removed from the top 100
        websites I visit, then I only risk seeing them rarely, which is practically
        equivalent to never. Great... so I only need to write rules for 100 websites.
        I'm lazy, so that's about 100 too many, and I'm unwilling to invest the time
        unless I'm on an OCD stint.</p>
        <p>What I really want is for something or someone else to do the work of writing
        the rules so I can enjoy the fruits of their labor without lifting a finger
        myself. I can think of three realistic options:</p>
        <ol>
        <li>Outsource the work to a hard-working, altruistic benefactor who takes
        ownership of the rules. This is akin to filter lists used by ad blockers.
        Tens of someones do the hard, thankless work of writing and updating these
        lists to save hundreds of millions of people billions of hours annually. In
        big-tech world where quantifying impact is supposedly important, those
        numbers are crazy to think about. If you care about effective charity, then
        maybe these someones deserve something.</li>
        <li>Crowdsource the rules so that many less hard-working people can contribute a
        little. Wikipedia, SponsorBlock, and crowd-sources translation platforms are
        prominent examples. The challenge here is moderation, preventing abuse, and
        filtering low-quality contributions. SponsorBlock is interesting because
        there is a right answer: given a video timeline, there are specific objective
        time segments containing sponsored ads. This makes crowdsourcing a little
        easier. Wikipedia and crowd-translation platforms are a little tricker and
        require heavier moderation because there is no right answer. I think website
        content rules lean toward the objective, but there are still multiple ways to
        achieve the same result.</li>
        <li>Have machines devise the rules. What was unachievable just 2 years ago can
        suddenly be done with the power of AI, particularly gen-AI. Whereas in the
        past you might attempt to create heuristic algorithms that sort of worked
        half the time, today you can use gen-AI, and it works great most of the time.
        You just have to figure out how to get data into the system and pay for GPUs.</li>
        </ol>
        <p>These options are not mutually exclusive and in my opinion are all worth
        exploring. Once you have some sort of system for defining rules, the next
        question is how do you make them accessible to users. A web extension is the
        obvious answer, but how should it be designed so that users can easily
        understand and customize rules to their preference? That will have to wait for
        my next blog post.</p>
        ]]>
      </content:encoded>
      <pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate>
      <atom:updated>2024-03-07T00:00:00.000Z</atom:updated>
    </item>
    <item>
      <title>Design of this Website</title>
      <link>https://dawoodjee.com/blog/design-of-this-website/</link>
      <guid isPermaLink="false">https://dawoodjee.com/blog/design-of-this-website/</guid>
      <description>How and why I built this website</description>
      <content:encoded>
        <![CDATA[<p>For a while now, I’ve wanted to blog about topics I think are interesting or
        might be useful to others. This post explains why I needed a website and how I
        built it.</p>
        <h2 id="why-not-social-media-or-hosted-cmss" tabindex="-1"><a href="https://dawoodjee.com/blog/design-of-this-website/#why-not-social-media-or-hosted-cmss" class="header-anchor">Why not Social Media or Hosted CMSs</a></h2>
        <p>Ownership is important to me because I want to control how my content is used
        and communicated. I don’t want my content presented alongside ads, cookie
        banners, subscription dialogs, or algorithmic feeds optimized for engagement.
        I’m fortunate enough to be able to offer my content free of charge or
        encumbrances and hopefully it’s worth that price. This purism precludes using
        social media platforms like Medium, Substack, or Dev Community.</p>
        <p>This left me with the choice of either a hosted CMS like Wordpress or the tried,
        old method of a custom website. Unfortunately, the easy hosted option isn’t for
        me since I prefer managing content in markdown files that can be opened with a
        local text editor, version-controlled with Git, and understood by many useful
        tools. The hosted option would also likely involve battling an unfamiliar
        monolith to achieve my preferred customizations. Fortunately, as a professional
        software dude, I know how to build a website.</p>
        <h2 id="the-static-website-tech-stack" tabindex="-1"><a href="https://dawoodjee.com/blog/design-of-this-website/#the-static-website-tech-stack" class="header-anchor">The Static Website Tech Stack</a></h2>
        <p>Maintenance is the bane of my existence, so servers that might go down and
        services that require an up-to-date credit card are out of the question. Luckily
        GitHub Pages exists, and it offers free static website hosting. It just means my
        website won’t support dynamic content, which I don’t want anyways because for
        blogs, dynamic content usually means social features like commenting, liking,
        and sharing. I think social features, which are usually designed to promote
        engagement and distribution, are almost always anti-features, since they
        distract from the main message and consume users’ limited attention.</p>
        <p>Knowing I wanted a static website, the next step was to decide on a static
        website builder. Because TypeScript is my most productive language and I hate
        wasting time on setup and tooling, I decided to use <a href="https://deno.com/">Deno</a>, a
        batteries-included TypeScript runtime. I Googled “deno ssg,” found
        <a href="https://lume.land/">Lume</a>, and never looked back because <em>it just worked</em>.</p>
        <p>Lume is just another static site generator. Raw content files go in, beautiful
        (or, if we’re being honest, usually ugly) website comes out. In my case, I
        wanted to use markdown to write blog posts and JSX to define its presentation.
        Markdown is simple, reduces the friction of writing and editing rich(ish) text
        documents, and is well supported by editors and tooling. JSX is simple,
        powerful, and saves me from having to learn bespoke templating languages of
        which Lume has its own. Lume natively supports Markdown and setting up the
        first-party JSX plugin took me 5 minutes.</p>
        <p>Setting up hosting and DNS to actually get the static websites deployed at
        https://dawoodjee.com was straightforward. I use Namecheap for domain
        registration since it’s done the job well for me for years, Cloudflare for easy
        DNS configuration and fast nameservers, and Lume’s stock template for deploying
        to GitHub Pages with GitHub Actions.</p>
        <p>I could have reduced complexity and save an hour by deploying to a github.io
        subdomain, but using my own domain helps me retain control of my content. I can
        shift the underlying infrastructure, perhaps by switching from Github pages to
        GitLab Pages, without breaking the user-facing API: URLs.</p>
        <h2 id="designing-the-website" tabindex="-1"><a href="https://dawoodjee.com/blog/design-of-this-website/#designing-the-website" class="header-anchor">Designing the Website</a></h2>
        <p>With the basic tech stack decided, the next step was to design the website. I
        followed this principle: less <em>is</em> more because extraneous content distracts
        from the core message. There would be no about page, no contacts page, no
        hamburgers or kabobs; just a home page with basic information and links to blog
        posts.</p>
        <p>Branding helps with recognition, so I added a simple tuxedo cat avatar to the
        top of the page that also serves as a link to the home page. I created a footer
        with a few useful tools: a search box (that directs queries to a site-scoped
        Google search), a link to the page’s underlying file in GitHub in case I want to
        make a quick edit or someone identifies a typo, and feed links for eccentrics
        like me.</p>
        <p>Blog posts follow this basic structure: first the title, then basic metadata,
        then tags, and finally the content. I call it “basic metadata” because I think
        basically everything on the internet should have it. It peeves me when an
        article is not accompanied by its author or publish date. Why should I trust
        whatever is written if no accountable name is attached? How do you know whether
        the information is still relevant or hopelessly outdated? I considered adding
        time-to-read estimates, but decided not to, since it’s unclear to me if they are
        actually useful. Email me if you know a compelling reason to include them.</p>
        <p>I have mixed feelings about tags because it’s hard to decide on and easy to miss
        appropriate tags. I chose to include them for now because they help readers
        quickly identify topics that <em>aren’t</em> of interest. I look forward to a future in
        which tags are AI-generated based on the content of blog posts. The future is
        probably the present, but Lume doesn’t have a first-party plugin.</p>
        <h2 id="retrospective" tabindex="-1"><a href="https://dawoodjee.com/blog/design-of-this-website/#retrospective" class="header-anchor">Retrospective</a></h2>
        <p>Well, dawoodjee.com is online and I’m on the tail end of my first blog post. Did
        I succeed? In terms of building a blog in which I retain full control, Yes. In
        terms of building a compelling blogging experience, Yes. In terms of building a
        low maintenance website… I’m optimistic but uncertain. I’ll check back again
        later, perhaps in a year, to see if I’m still happy. Hopefully nothing breaks
        and Dependabot security alerts skip my inbox.</p>
        ]]>
      </content:encoded>
      <pubDate>Tue, 20 Feb 2024 00:00:00 GMT</pubDate>
      <atom:updated>2024-03-07T00:00:00.000Z</atom:updated>
    </item>
  </channel>
</rss>