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