<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Frogtoss Labs</title><link>https://www.frogtoss.com/labs/</link><description/><atom:link href="https://www.frogtoss.com/labs/feeds/all.rss.xml" rel="self"/><lastBuildDate>Sun, 05 Jul 2026 18:21:00 -0700</lastBuildDate><item><title>Handmade Network Expo Grain DDL Demo</title><link>https://www.frogtoss.com/labs/handmade-expo-grain-ddl-demo.html</link><description>&lt;p&gt;I was invited to demo Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; at the Handmade Network Expo in Vancouver this year.  What followed was 20 minutes sharing what Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; is, and some pretty unique examples of how a code generator can be&amp;nbsp;used.&lt;/p&gt;
&lt;p&gt;Come watch the talk &lt;a href="https://www.youtube.com/watch?v=qfEzUnZlMIo"&gt;on Youtube&lt;/a&gt; and check out the newly launched &lt;a href="https://www.frogtoss.com/grain"&gt;product page&lt;/a&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Michael Labbe</dc:creator><pubDate>Sun, 05 Jul 2026 18:21:00 -0700</pubDate><guid>tag:www.frogtoss.com,2026-07-05:/labs/handmade-expo-grain-ddl-demo.html</guid><category>Blog</category><category>grain</category><category>link</category></item><item><title>Grain and Value Specialization</title><link>https://www.frogtoss.com/labs/grain-and-value-specialization.html</link><description>&lt;p&gt;A designer who worked on a &lt;span class="caps"&gt;AAA&lt;/span&gt; open-world game once remarked that he spent weeks implementing a sweeping behaviour change across every traffic light in the world.  A design change made it necessary to re-code every traffic intersection in the game to support right-on-red&amp;nbsp;traffic.&lt;/p&gt;
&lt;p&gt;When he proposed that the next game be built with common traffic light logic that applied to all lights, the counterargument was: &amp;#8220;if it breaks in one place, it&amp;#8217;ll break everywhere&amp;#8221;.  His response: &amp;#8220;If it breaks everywhere, we&amp;#8217;ll know exactly where to go to fix&amp;nbsp;it.&amp;#8221;&lt;/p&gt;
&lt;p&gt;Practising developers find that the truth lies somewhere in between, and it depends on your project. Your tools must empower you to be productive wherever on that spectrum you find&amp;nbsp;yourself.&lt;/p&gt;
&lt;p&gt;&lt;a href="/grain"&gt;Grain &lt;span class="caps"&gt;DDL&lt;/span&gt;&lt;/a&gt; 0.2.0 (pre-alpha test) now offers a technique called value specialization.  This allows you to declare a type that has the exact same fields, in the same order as a base struct, but with the ability to explicitly override the values.  No new values can be&amp;nbsp;added.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// a is copied from A.a&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This produces the following&amp;nbsp;values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;A.a = 10&lt;span class="eol"&gt;&lt;/span&gt;A.b = 20&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;B.a = 10&lt;span class="eol"&gt;&lt;/span&gt;B.b = 30 &lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is &amp;#8220;value specialization for PODs&amp;#8221; &amp;#8212; a flat memory model where only plain old data is subject to being&amp;nbsp;overridden.&lt;/p&gt;
&lt;p&gt;Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; is designed to let you generate anything you want from its source data and types.  But it keeps you in check about what you can define to keep you generating simple, effective code.  In order to use value specialization to its maximum advantage, &lt;strong&gt;only the values can change&lt;/strong&gt;, and not the types.&amp;nbsp;Consider:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// error: A.c does not exist and new fields not possible&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// error: A.b exists, but it is i32, not f32&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;f32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In the case&amp;nbsp;of &lt;code&gt;C.b&lt;/code&gt;, you might be asking &amp;#8220;why are you asking the user to restate the type in the derived struct, if it has to match?&amp;nbsp;&amp;#8220;&lt;/p&gt;
&lt;p&gt;When creating just a few instances&amp;nbsp;by &lt;code&gt;copies&lt;/code&gt;, this will seem like an unnecessary burden.  However, once you have quite a few, a sense of base type calcification naturally occurs: &lt;em&gt;if I change the type of this field in the base, how many downstream things could break&lt;/em&gt;?&lt;/p&gt;
&lt;p&gt;Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; has implicit casting of expressions resulting in scalar values &amp;#8212; albeit with full checks for overflow and underflow at compile time.  However, in the event that a type changes in a copied struct, it becomes important to comb over the code and ensure that all of the values are also correctly and adequately typed.  This requires a touchpoint from a developer.  No silent truncation is&amp;nbsp;possible.&lt;/p&gt;
&lt;p&gt;The compiler catches the type mismatch until you visit the line.  By changing the type, you are signing off that the declared value is compatible with the type&amp;nbsp;change.&lt;/p&gt;
&lt;h2&gt;Late-Stage&amp;nbsp;Binding&lt;/h2&gt;
&lt;p&gt;One of the timeless functions of spreadsheets is relative referencing.  If you have a formula&amp;nbsp;like &lt;code&gt;=A1+B1&lt;/code&gt; and drag the selection box downwards, the next field will&amp;nbsp;be &lt;code&gt;=A2+B2&lt;/code&gt;.  This is used to great effect in calculating the same formula for a different set of&amp;nbsp;values.&lt;/p&gt;
&lt;p&gt;Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; uses late-stage binding.  When you have a base struct with a formula that has field names in it and it is derived&amp;nbsp;via &lt;code&gt;copies&lt;/code&gt;, &lt;em&gt;the formula is calculated in the context of the values in the derived struct&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Consider how sum behaves on override&amp;nbsp;of &lt;code&gt;b&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;B&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;copies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;i32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// instead of 20&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;// sum calculated here with &amp;#39;b&amp;#39; overridden to 30&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This produces the following&amp;nbsp;values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;A.a = 10&lt;span class="eol"&gt;&lt;/span&gt;A.b = 20&lt;span class="eol"&gt;&lt;/span&gt;A.sum = 30    &amp;lt;- sum of A.a + A.b&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;B.a = 10&lt;span class="eol"&gt;&lt;/span&gt;B.b = 30&lt;span class="eol"&gt;&lt;/span&gt;B.sum = 40    &amp;lt;- sum of B.a + B.b&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This is a powerful feature that permits you to model relationships between values in one place, and have that cascade.  It is designed to run as a precompile step, so the result of your computation is simply placed in your code and values are easily&amp;nbsp;reviewable.&lt;/p&gt;
&lt;p&gt;Said another way, the values are computed at emit time, and can be reviewed as plain&amp;nbsp;numbers.&lt;/p&gt;
&lt;h2&gt;Putting it Together With Codegen&amp;nbsp;Templates&lt;/h2&gt;
&lt;p&gt;Grain &lt;span class="caps"&gt;DDL&lt;/span&gt; lets you generate anything you need by specifying&amp;nbsp;a &lt;code&gt;.cgtmpl&lt;/code&gt; file at compile&amp;nbsp;time.&lt;/p&gt;
&lt;p&gt;When iterating over&amp;nbsp;structs, &lt;code&gt;.copy_name&lt;/code&gt; gets the name of the&amp;nbsp;struct:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[_ &lt;span class="eol"&gt;&lt;/span&gt;range .structs&lt;span class="eol"&gt;&lt;/span&gt;  if .copy_name&lt;span class="eol"&gt;&lt;/span&gt;    .name ` copies ` .copy_name ``.&lt;span class="eol"&gt;&lt;/span&gt;  else&lt;span class="eol"&gt;&lt;/span&gt;    .name ` does not copy`.&lt;span class="eol"&gt;&lt;/span&gt;  end&lt;span class="eol"&gt;&lt;/span&gt;end&lt;span class="eol"&gt;&lt;/span&gt;_]&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Produces&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;A does not copy&lt;span class="eol"&gt;&lt;/span&gt;B copies A&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Live Sample&amp;nbsp;Code&lt;/h3&gt;
&lt;p&gt;See example &amp;#8220;Copies Keyword&amp;#8221; in the Grain &lt;span class="caps"&gt;IDE&lt;/span&gt; for a demonstration of how weapons in a game use value specialization to create one type, calculate &lt;span class="caps"&gt;DPS&lt;/span&gt; from a set of factors, and produce simple C definitions that reduce the complexity of a game&amp;nbsp;runtime.&lt;/p&gt;
&lt;h2&gt;On Traffic Lights and Best&amp;nbsp;Practices&lt;/h2&gt;
&lt;p&gt;I believe my colleague&amp;#8217;s desire to have all of the traffic lights in an &lt;span class="caps"&gt;AAA&lt;/span&gt; open world operate from the same common logic was directionally correct.  However, if you are building a hundred intersections in an open world, I would recommend using a system like Grain &lt;span class="caps"&gt;DDL&lt;/span&gt;&amp;#8217;s value specialization to define them, keeping in&amp;nbsp;mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Code-Generate a single base structure &amp;#8212; all traffic lights are one fat struct, where the difference is in the values rather than through tagged&amp;nbsp;discrimination.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use Grain &lt;span class="caps"&gt;DDL&lt;/span&gt;&amp;#8217;s late-stage bindings to formulate values and then generate compile-time asserts to ensure they remain in acceptable ranges to catch problems at compile time rather than &lt;span class="caps"&gt;QA&lt;/span&gt;&amp;nbsp;time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Consider that some traffic intersections may be so different they &lt;em&gt;should&lt;/em&gt; be special-case coded and not part of the&amp;nbsp;system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use Grain &lt;span class="caps"&gt;DDL&lt;/span&gt;&amp;#8217;s type checking system to make incremental, verified structural alterations as&amp;nbsp;needed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Help Develop&amp;nbsp;Grain&lt;/h2&gt;
&lt;p&gt;Grain is currently in closed pre-alpha testing.  If you would like to try it or share feedback,&amp;nbsp;email &lt;code&gt;grain _at_ frogtoss dot com&lt;/code&gt;.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Michael Labbe</dc:creator><pubDate>Thu, 30 Apr 2026 13:10:00 -0700</pubDate><guid>tag:www.frogtoss.com,2026-04-30:/labs/grain-and-value-specialization.html</guid><category>Blog</category><category>grain</category><category>code</category></item><item><title>You Can’t Promote a Programming Language By Its Features Alone</title><link>https://www.frogtoss.com/labs/you-can't-promote-a-programming-language-by-its-features-alone.html</link><description>&lt;p&gt;It is deceptively easy to make the mistake of attributing a language&amp;#8217;s popularity solely to the syntax and functional features of the language.  In reviewing why languages became successful, there is often a retroactive feature-driven mythology wherein we over-attribute the success of a language to its unique syntax and functionality.  Simultaneously, we under-attribute the environment the language was in at the time of its early maturity to its eventual&amp;nbsp;uptake.&lt;/p&gt;
&lt;p&gt;Ultimately, no programming language has become highly adopted on function and syntax alone, and so it does not make sense to retroactively attribute its success solely to function and syntax.  Its ascent must be contextualized with the era of its uptake to be well&amp;nbsp;understood.&lt;/p&gt;
&lt;p&gt;The adoption of a language toolchain takes significant effort for a team or an org to achieve, and some threshold must be crossed to make the integration last.  While language features can &lt;em&gt;lower&lt;/em&gt; this threshold, it is the environment in which the language flourishes that justifies the &lt;em&gt;overcoming of the threshold&lt;/em&gt; in the first place.  This forward momentum &amp;#8212; the &lt;em&gt;why&lt;/em&gt; &amp;#8212; is essential for&amp;nbsp;adoption.&lt;/p&gt;
&lt;p&gt;In practice, the most popular languages we use are the result of the intersection of trends in computing with the function of the language.  It is informative to look at the environment in which each of these languages&amp;nbsp;flourished.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; C was the best language for hardware-agnostic Unix programming at the time when Unix was taking off.  Low-overhead Unix syscall compatibility and direct memory access were a fit for the everything-is-a-file Unix trend, whereas Pascal was weaker in these crucial&amp;nbsp;areas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C++&lt;/strong&gt; By the 1990s, programs were increasingly created by larger teams. C++ offered syntactic boundaries that allowed programs to map to team structure.  Additionally, the 1990s were a golden age for selling compilers in boxes at retail, enabling unparalleled awareness of the language.  GUIs became commonplace, and the &lt;span class="caps"&gt;OOP&lt;/span&gt; additions to C++ were perceived as useful for composing graphical&amp;nbsp;interfaces.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Perl&lt;/strong&gt; &lt;span class="caps"&gt;CGI&lt;/span&gt; needed something more practical for extraction and reporting than Bash. JavaScript was effectively unreliable in web browsers of the era, so sophisticated full-page server-side rendering was the order of the day.  Late &amp;#8216;90s cheap shared hosting democratized server access.  The availability of MySQL and Postgres clinched the deal, and the &lt;span class="caps"&gt;LAMP&lt;/span&gt; stack was&amp;nbsp;born.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python 2&lt;/strong&gt; By the 2000s, scripts were increasingly created by larger teams. Python 2 offered syntactic constructs that made it easy to compose long-running scripts in a more readable and maintainable way than&amp;nbsp;Perl.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python 3&lt;/strong&gt; PyData/&lt;span class="caps"&gt;ML&lt;/span&gt; stack became institutional.  As data science and &lt;span class="caps"&gt;ML&lt;/span&gt; hiring surged in the 2010s, Python 3 became the default research-to-production path.  Many new developers may even think of NumPy/SciPy as being intrinsic to the&amp;nbsp;toolchain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ruby (on Rails)&lt;/strong&gt; Developers needed to iterate quickly in the problem space of web service business. This happened at a time when VCs infused cash into a cultural movement that emphasized iterating quickly to find product-market&amp;nbsp;fit.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt; The web browser is the open, universal runtime we got, and JavaScript had a twenty-year head start on any real alternative.  (Sorry, Java&amp;nbsp;Applets).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Java&lt;/strong&gt; The onboarding of businesses to the Internet happened at a time when a memory-safe language was needed for network-aware programming by large numbers of programmers.  The influx of many freshly graduated compsci majors who found direct memory manipulation challenging could be put to work to deliver real corporate value on large teams. Java&amp;#8217;s timing was serendipitous for the enterprise&amp;nbsp;build-out.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C#&lt;/strong&gt; Microsoft, at a time when they were the most dominant software vendor in the world, &lt;a href="https://www.youtube.com/watch?v=8fcSviC7cRM"&gt;massively backed developers&lt;/a&gt; during &amp;#8220;dot com&amp;#8221; layoffs and early 2000s corporate downsizing.  They emphatically showed the Windows platform-supported way of doing memory-safe programming.  Using &lt;span class="caps"&gt;COM&lt;/span&gt; or Java to build Windows was now off-book.  Out-of-work developers took them seriously and reskilled with C#.  Unity later bet the farm on C# which was a second shot in the arm for the&amp;nbsp;language.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rust&lt;/strong&gt; Modern networked devices have to deal with increasingly diverse untrusted data processing (such as bluetooth packets, fonts, audio streaming). Major industry players such as Microsoft, Google and &lt;span class="caps"&gt;AWS&lt;/span&gt; have sought to reduce memory-safety-related CVEs in &amp;#8220;zero-click&amp;#8221; attack surfaces without the use of garbage&amp;nbsp;collection.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A lesson we can take from this is that you can&amp;#8217;t market a language a position of popularity by simply espousing its features and qualities in public.  You can, however, build a language and the foundations of its toolchain and help it hitch onto a greater trend.  Changing needs are what prompt teams to overcome the friction involved in upgrading their software development&amp;nbsp;tooling.&lt;/p&gt;
&lt;p&gt;As always, timing is&amp;nbsp;key.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Michael Labbe</dc:creator><pubDate>Wed, 07 Jan 2026 11:15:00 -0800</pubDate><guid>tag:www.frogtoss.com,2026-01-07:/labs/you-can't-promote-a-programming-language-by-its-features-alone.html</guid><category>Blog</category><category>marketing</category></item><item><title>Printable Base89 LUT Encoding</title><link>https://www.frogtoss.com/labs/printable-base89-lut-encoding.html</link><description>&lt;p&gt;&lt;img alt="diagram" src="https://www.frogtoss.com/labs/postimages/b89-encoding.png"/&gt;&lt;/p&gt;
&lt;h2&gt;Need&lt;/h2&gt;
&lt;p&gt;In state logic or semantic markup with text strings, there are two
options: in-band formatting, or out of band&amp;nbsp;formatting.&lt;/p&gt;
&lt;p&gt;In-band formatting indicates changes in a string.  For&amp;nbsp;instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Hello &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Fred&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;span&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Out-of-band markup involves describing a range of characters as
having properties as part of a separate data&amp;nbsp;structure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello Fred&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="n"&gt;Span&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;spans&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;first_name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// chars 6-9 form &amp;quot;Fred&amp;quot;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Out-of-band markup is ultimately more efficient, but in-band markup
has the advantage of being incredibly handy to author in an ad-hoc
way, such as when putting together a string literal in a&amp;nbsp;program.&lt;/p&gt;
&lt;p&gt;Unfortunately, all of the in-band markup languages are intended for a
specific output environment, such as &lt;span class="caps"&gt;ANSI&lt;/span&gt; escape sequences or &lt;span class="caps"&gt;HTML&lt;/span&gt;,
and the definitions of them are large, but also limited to the
intended use of the defining&amp;nbsp;parties.&lt;/p&gt;
&lt;p&gt;Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; Encoding addresses this.  You embed indices into
a lookup table in your string.  What is in the lookup table is up to
you, the&amp;nbsp;developer.&lt;/p&gt;
&lt;p&gt;The following spec only describes how to embed the lookup table
indices in a string, not what you could do with it.  In practice it is
useful for a few&amp;nbsp;things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Semantic markup for&amp;nbsp;theming&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Styled output that can be emitted to in-game text rendering,
   console or a browser including font swapping, kinetic type styling
   and rendering&amp;nbsp;transforms&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Denoting positional arguments for&amp;nbsp;localization&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&amp;#8217;s just an index into your lookup table.  You&amp;nbsp;decide.&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; encoding is used to embed indices into &lt;span class="caps"&gt;UTF&lt;/span&gt;-8
encoded strings.  The lookup table&amp;#8217;s use is left up to the&amp;nbsp;user.&lt;/p&gt;
&lt;p&gt;Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; encoded control codes have the following&amp;nbsp;properties:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The purpose of each index is ascribed by the developer, not the
   encoding&amp;nbsp;designer&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Embeddable in &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 encoded&amp;nbsp;strings&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Viable for systems dev: All control codes display correctly in
   Visual Studio, gdb, and in&amp;nbsp;terminals&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Viable for web dev: All control codes log correctly in&amp;nbsp;browsers&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Trivially skippable: Constant 4-byte length, unlike &lt;span class="caps"&gt;ANSI&lt;/span&gt;/&lt;span class="caps"&gt;VT&lt;/span&gt;&amp;nbsp;codes&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Specification includes error handling&amp;nbsp;behaviours&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Compatible by design with C preprocessor&amp;nbsp;concatenation&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Control&amp;nbsp;Code&lt;/h2&gt;
&lt;p&gt;A Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; control code has the following byte&amp;nbsp;sequence:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ST    C1    C2    C3&lt;span class="eol"&gt;&lt;/span&gt;03    xx    xx    xx&lt;span class="eol"&gt;&lt;/span&gt;^     ^           ^&lt;span class="eol"&gt;&lt;/span&gt;|     |           |&lt;span class="eol"&gt;&lt;/span&gt;|     LSB         MSB&lt;span class="eol"&gt;&lt;/span&gt;|&lt;span class="eol"&gt;&lt;/span&gt;constant&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;xx&lt;/code&gt; is a value&amp;nbsp;between &lt;code&gt;[38, 126]&lt;/code&gt;. &lt;code&gt;C1&lt;/code&gt; through &lt;code&gt;C3&lt;/code&gt; contain values
that combine to produce an index into a lookup&amp;nbsp;table. &lt;code&gt;C1&lt;/code&gt; is the
&lt;span class="caps"&gt;LSB&lt;/span&gt;, &lt;code&gt;C3&lt;/code&gt; is the &lt;span class="caps"&gt;MSB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C1&lt;/code&gt; through &lt;code&gt;C3&lt;/code&gt; all base89 packed integers.  They can have a value
between 38 and 126 (inclusive), for a total of 89 possible&amp;nbsp;values.&lt;/p&gt;
&lt;p&gt;Why these values?  The strings must be printable in any debugger, or
dumped to a terminal without being interpreted in a manner that makes
review difficult or impossible.  The range 38 to 126 are all normal
printable single byte &lt;span class="caps"&gt;UTF&lt;/span&gt;-8 characters.  In fact, 126 is the last
normal, printable&amp;nbsp;character.  &lt;/p&gt;
&lt;p&gt;But why 38 for a starting value?  37 is the ordinal number&amp;nbsp;of &lt;code&gt;%&lt;/code&gt;, which would need to be
escaped if inserted into a C format specifier string. When avoided, we
have a range of 89 possible values per&amp;nbsp;byte.&lt;/p&gt;
&lt;p&gt;The three values are combined,&amp;nbsp;with &lt;code&gt;C3&lt;/code&gt; being the &lt;span class="caps"&gt;MSB&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;This forms an index that can be used to lookup into a table which&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The maximum value is calculated&amp;nbsp;as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;max_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;89&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;704&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;968&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The base-10 byte pattern of the maximum value&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ST    C1    C2    C3&lt;span class="eol"&gt;&lt;/span&gt;03   126   126   126&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;the base-10 minimum value&amp;nbsp;is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ST    C1    C2    C3&lt;span class="eol"&gt;&lt;/span&gt;03    38    38    38&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The following caveats exist in correct processing of these&amp;nbsp;codes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Index 0 is reserved to indicate an error condition during
    unpacking.  If a code is malformed, index 0 is returned.  Make the
    0th index in your &lt;span class="caps"&gt;LUT&lt;/span&gt; handle&amp;nbsp;errors.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;any byte value&amp;nbsp;in &lt;code&gt;C1-C3&lt;/code&gt; outside of the&amp;nbsp;range &lt;code&gt;[38,126]&lt;/code&gt; results
    in an index of 0 &amp;#8212; error condition.  String processing continues
    after the&amp;nbsp;sequence.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If a Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; code truncates (string does not have
    three bytes remaining after &lt;span class="caps"&gt;ST&lt;/span&gt;), the index returned is 0 &amp;#8212; error
    condition.  String processing terminates, but the part of the
    string before the truncated Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; code is still&amp;nbsp;valid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The resulting index can be between 0 and 704968, but oftentimes
    the lookup will be much smaller.  If the index is out of range for
    a &lt;span class="caps"&gt;LUT&lt;/span&gt;, return index 0 &amp;#8212; error condition.  Continue string&amp;nbsp;processing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;There is no explicit support for escaping &lt;span class="caps"&gt;ST&lt;/span&gt;, eg.&amp;nbsp;having &lt;code&gt;0x03
    0x03&lt;/code&gt; in a string to produce a&amp;nbsp;single &lt;code&gt;0x03&lt;/code&gt; by a string
    processor.  If&amp;nbsp;verbatim &lt;code&gt;0x03&lt;/code&gt; is needed in a string for any
    reason, use a &lt;span class="caps"&gt;LUT&lt;/span&gt; index to emit&amp;nbsp;it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If control codes are not legal inside a part of a string (such as
    inside quotes), the string processor must handle this according to
    your quote processing&amp;nbsp;needs. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Encoding an out-of-range index is a runtime error to be handled
    appropriately.  Do not silently encode&amp;nbsp;0.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Example Use For Semantic&amp;nbsp;Markup&lt;/h2&gt;
&lt;p&gt;It is straightforward to define a Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; code as a string in C and use it in a preprocessor&amp;nbsp;concatenation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// note: \x27 is 0x27 which is 39 in base 10&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define CODE_RESET  &amp;quot;\x03\x27\x26\x26&amp;quot;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define CODE_NAME   &amp;quot;\x03\x28\x26\x26&amp;quot;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="cp"&gt;#define NAME(x) CODE_NAME x CODE_RESET&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Hello, &amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;guy&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ANSI_LUT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// index 0 is always error&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;ERROR&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// reset&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\x1b&lt;/span&gt;&lt;span class="s"&gt;[0m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// name - bold&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\x1b&lt;/span&gt;&lt;span class="s"&gt;[1m&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;HTML_LUT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// index 0 is always error&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;lt;ERROR&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// reset&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;lt;/span&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// name&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;lt;span class=&amp;#39;name&amp;#39;&amp;gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="n"&gt;output_ansi_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;span class="n"&gt;output_html_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="eol"&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2&gt;Admonishment:&amp;nbsp;Security&lt;/h2&gt;
&lt;p&gt;Exotic use leads to security&amp;nbsp;issues.&lt;/p&gt;
&lt;p&gt;Having the ability to look up into a table and perform arbitrary
processing while scanning strings will inevitably lead to exotic uses
and therefore security&amp;nbsp;abuses.&lt;/p&gt;
&lt;p&gt;The functions that actually process strings rarely have the ability to
determine whether the string has user input in it.  The reality is
that everything that manipulates up the stack needs to correctly
anticipate what an &lt;span class="caps"&gt;LUT&lt;/span&gt; can do to the whole system state.  Because a
&lt;span class="caps"&gt;LUT&lt;/span&gt; gets added to during development, managing what processing a user
can do to the system at the place in the code where the string is
assembled becomes intractable.  Do not do unmanageably crazy things in
your table&amp;nbsp;logic.&lt;/p&gt;
&lt;h2&gt;&lt;span class="caps"&gt;FAQ&lt;/span&gt;&lt;/h2&gt;
&lt;h3&gt;Why continue string processing after an error was&amp;nbsp;encountered?&lt;/h3&gt;
&lt;p&gt;Efficient string processing routines intended for output emit pieces
of a string to buffered streams as needed.  Doing string processing of
arbitrary length likely requires an allocation, and an additional
buffer, in addition to what the buffered stream is&amp;nbsp;doing.&lt;/p&gt;
&lt;p&gt;A reasonably efficient Printable Base89 &lt;span class="caps"&gt;LUT&lt;/span&gt; string processor scans a
string until an &lt;span class="caps"&gt;ST&lt;/span&gt; code is reached, emits the string up until that
point, processes the code, and then continues to scan.  This is
preferred to copying the string into a buffer and emitting it all at&amp;nbsp;once.&lt;/p&gt;
&lt;h3&gt;Is it possible to directly encode index&amp;nbsp;0?&lt;/h3&gt;
&lt;p&gt;&lt;span class="caps"&gt;LUT&lt;/span&gt; index 0 is reserved for the error case. You should not use it for
a normal sequence.  It is possible to encode index zero using the&amp;nbsp;sequence &lt;code&gt;0x03 0x26 0x26 0x26&lt;/code&gt; in your string, though there is no
reason to do&amp;nbsp;so.&lt;/p&gt;
&lt;p&gt;This is preferred to adding one to the processed code, which just adds&amp;nbsp;complexity.&lt;/p&gt;
&lt;h3&gt;What happens if untrusted input contains a control&amp;nbsp;code?&lt;/h3&gt;
&lt;p&gt;All untrusted input needs to scan for the &lt;span class="caps"&gt;ST&lt;/span&gt; &lt;code&gt;0x03&lt;/code&gt; byte, and have it
validated or&amp;nbsp;removed.&lt;/p&gt;
&lt;p&gt;This is nothing new.  &lt;span class="caps"&gt;ANSI&lt;/span&gt; escape sequences in untrusted user input can also affect output, and need to be validated or&amp;nbsp;removed.&lt;/p&gt;
&lt;h3&gt;If I Implement This, Where Should I Link to the&amp;nbsp;Spec?&lt;/h3&gt;
&lt;p&gt;This article&amp;#8217;s slug is the permalink you are looking&amp;nbsp;for.&lt;/p&gt;
&lt;h2&gt;Links To&amp;nbsp;Implementations&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;C single header file:&lt;/strong&gt; &lt;a href="https://github.com/frogtoss/ftg_toolbox_public/blob/main/ftg_base89.h"&gt;ftg_base89.h&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python module&lt;/strong&gt; &lt;a href="https://github.com/frogtoss/py-base89lut"&gt;py-base89lut&lt;/a&gt; &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Michael Labbe</dc:creator><pubDate>Mon, 20 Oct 2025 09:57:00 -0700</pubDate><guid>tag:www.frogtoss.com,2025-10-20:/labs/printable-base89-lut-encoding.html</guid><category>Blog</category><category>code</category></item><item><title>Software Adoption Revolutions Come from Architectural Shifts, Not Performance Bumps</title><link>https://www.frogtoss.com/labs/software-adoption-revolutions-come-from-architectural-shifts-not-performance-bumps.html</link><description>&lt;p&gt;Choosing what to work on is one of the most interesting parts of building software.  As developers, we often see tooling with suboptimal efficiency and aspire to rewrite it.  In many cases, the performance gains can be substantial &amp;#8212; but historically it has been user productivity gains that drive adoption in the large, not simply execution&amp;nbsp;performance.&lt;/p&gt;
&lt;p&gt;When looking at the history of software adoption revolutions, the productivity of the user was the dominating factor in making significant, sweeping changes to how we develop.  Execution time, exclusive of other improvements, is commonly not sufficient to increase productivity enough to produce a large-scale software adoption&amp;nbsp;revolution.&lt;/p&gt;
&lt;p&gt;The more impactful thing we can do in designing software is to rethink the solution to the problem from first&amp;nbsp;principles.&lt;/p&gt;
&lt;p&gt;Key examples in the history of software that shaped current usage at&amp;nbsp;large:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Interactive time-sharing terminals&lt;/strong&gt; replaced batch-processed punch cards, giving a 10x increase in the number of feedback cycles in a day.  Faster punch card processing would not have competed with this&amp;nbsp;shift.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Software purchased and updated online&lt;/strong&gt;: more stores and efficient disc storage could not win over the high convenience of automated bit synchronization afforded by platforms like&amp;nbsp;Steam.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Owning server racks versus on-demand cloud&lt;/strong&gt;: In many cases, developers traded provisioning time and cost for execution time and cost.  This enabled a generation of developers to explore a business problem space without making a capital-intensive commitment to purchase server hardware&amp;nbsp;upfront.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Virtualization&lt;/strong&gt;: Isolated kernel driver testing, sandboxed bug reproduction, and hardware simulation greatly reduced the provisioning time necessary to reproduce system bugs.  This underpins the explosion in kernel fuzzing and systems continuous integration (&lt;span class="caps"&gt;CI&lt;/span&gt;).  Faster computers alone would not have delivered the same productivity&amp;nbsp;multiplier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Live linked game assets&lt;/strong&gt;: Artists get real-time feedback when they make geometry changes to assets in modelling tools.  While optimized exporters and importers are still important, this alone could not result in the productivity multiplier that comes from real-time&amp;nbsp;feedback.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Declarative, immutable runtimes&lt;/strong&gt;: Docker vastly reduced environment setup time.  Consequently, this massively reduced guesswork as to whether a bug was isolated to a mutable execution environment.  Highly optimized test environment provisioning would not have produced these productivity gains at the same&amp;nbsp;scale.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Some of these productivity improvements, unfortunately, came with performance &lt;em&gt;regressions&lt;/em&gt;.  And yet, the software adoption revolutions occurred&amp;nbsp;nonetheless.&lt;/p&gt;
&lt;p&gt;Much of the software we use could benefit from being significantly faster.   Reimplementing that software for better performance is like running farther in the same direction.  Rearchitecting the software is like running in an entirely different direction.  You should go as far as you can in the best&amp;nbsp;direction.&lt;/p&gt;
&lt;p&gt;We should write performant software because computing should be enjoyable, because slow software inefficiencies add up, and because high performance software is a competitive edge.  The first step, however, is to consider how best to solve the problem.  Performance improvements alone may not be deeply persuasive to large groups of users seeking&amp;nbsp;productivity.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Michael Labbe</dc:creator><pubDate>Tue, 30 Sep 2025 12:09:00 -0700</pubDate><guid>tag:www.frogtoss.com,2025-09-30:/labs/software-adoption-revolutions-come-from-architectural-shifts-not-performance-bumps.html</guid><category>Blog</category><category>code</category></item></channel></rss>