<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Josh Holbrook</title>
    <description>The latest articles on DEV Community by Josh Holbrook (@jfhbrook).</description>
    <link>https://dev.to/jfhbrook</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F130847%2F2fc5345a-75d2-450b-8e77-710509dcac62.jpg</url>
      <title>DEV Community: Josh Holbrook</title>
      <link>https://dev.to/jfhbrook</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jfhbrook"/>
    <language>en</language>
    <item>
      <title>Matanuska ADR 004 - Expect Tests</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Thu, 25 Dec 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-004-expect-tests-2557</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-004-expect-tests-2557</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Effective interpreters need a lot of tests. One kind of test is an "expect" test - run a script, potentially enter input programmatically, and assert the output.&lt;/p&gt;

&lt;p&gt;My test framework, node-tap, has a &lt;code&gt;matchSnapshot&lt;/code&gt; functionality which can assert the output. Programmatic input is more complicated, and may require another module.&lt;/p&gt;

&lt;p&gt;A type of test recommended by &lt;code&gt;Writing Interactive Compilers &amp;amp; Interpreters&lt;/code&gt; includes running absurdly large programs and asserting they cause meaningful errors instead of segmentation faults or similar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;I will write a series of test scripts in the test directory. A tap test will run each script and assert the output with &lt;code&gt;matchSnapshot&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These scripts will initially not take input, since solving for "expect" use cases is more complicated. This may be tackled in the future.&lt;/p&gt;

&lt;p&gt;There will also be (a) script(s) which generate absurdly large programs, which test the limits of the interpreter.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>testing</category>
    </item>
    <item>
      <title>ADR 019 - Identifier Contant Optimization</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Mon, 11 Aug 2025 00:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/adr-019-identifier-contant-optimization-59m2</link>
      <guid>https://dev.to/jfhbrook/adr-019-identifier-contant-optimization-59m2</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;While working on looping, I discovered an interesting behavior in compilation of global variable access. Consider the following program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 rem A simple for loop
20 for i% = 1 to 10
30   print i%
40 endfor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This compiles to the following disassembled bytecode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;20  1   CONSTANT       i%
20  3   CONSTANT       1
20  5   DEFINE_GLOBAL  i%
20  7   CONSTANT       i%
20  9   GET_GLOBAL     i%
20  11  CONSTANT       10
20  13  LE
20  14  JUMP_IF_FALSE  14 -&amp;gt; 44
20  17  POP
20  18  JUMP           18 -&amp;gt; 36
20  21  CONSTANT       i%
20  23  CONSTANT       i%
20  25  GET_GLOBAL     i%
20  27  CONSTANT       1
20  29  ADD
20  30  SET_GLOBAL     i%
20  32  POP
20  33  LOOP           33 -&amp;gt; 7
30  36  CONSTANT       i%
30  38  GET_GLOBAL     i%
30  40  PRINT
40  41  LOOP           41 -&amp;gt; 21
40  44  NIL
40  45  RETURN
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the following constants table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;constant index&lt;/th&gt;
&lt;th&gt;value&lt;/th&gt;
&lt;th&gt;relevant instructions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;1 CONSTANT&lt;/code&gt;, &lt;code&gt;5 DEFINE_GLOBAL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3 CONSTANT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;7 CONSTANT&lt;/code&gt;, &lt;code&gt;9 GET_GLOBAL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;11 CONSTANT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;21 CONSTANT&lt;/code&gt;, &lt;code&gt;30 SET_GLOBAL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;23 CONSTANT&lt;/code&gt;, &lt;code&gt;25 GET_GLOBAL&lt;/code&gt;, &lt;code&gt;29 ADD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;27 CONSTANT&lt;/code&gt;, &lt;code&gt;29 ADD&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i%&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;36 CONSTANT&lt;/code&gt;, &lt;code&gt;38 GET_GLOBAL&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The op codes are perhaps confusing, but correct. However, we do note that there are seven constants. The constants for &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;10&lt;/code&gt; and &lt;code&gt;1&lt;/code&gt; are expected - those are the start, end and increment respectively. But what about the five instances of &lt;code&gt;i%&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;On inspection, we will notice that &lt;code&gt;i%&lt;/code&gt; is defined once per each call to &lt;code&gt;DEFINE_GLOBAL&lt;/code&gt;, &lt;code&gt;GET_GLOBAL&lt;/code&gt; and &lt;code&gt;SET_GLOBAL&lt;/code&gt;, respectively. &lt;code&gt;DEFINE_GLOBAL&lt;/code&gt; is called once (to define &lt;code&gt;i%&lt;/code&gt;), &lt;code&gt;GET_GLOBAL&lt;/code&gt; is called thrice (once to compare to &lt;code&gt;10&lt;/code&gt;, once to add &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;i%&lt;/code&gt;'s value, once to print &lt;code&gt;i%&lt;/code&gt;), and &lt;code&gt;SET_GLOBAL&lt;/code&gt; is called once (to increment &lt;code&gt;i%&lt;/code&gt; by one). This behavior is simply unoptimized.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenge
&lt;/h2&gt;

&lt;p&gt;It turns out that's intentional within &lt;code&gt;Crafting Interpreters&lt;/code&gt;, the primary reference used for implementing this bytecode. In its "Challenges" section, it notes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The compiler adds a global variable’s name to the constant table as a string every time an identifier is encountered. It creates a new constant each time, even if that variable name is already in a previous slot in the constant table. That’s wasteful in cases where the same variable is referenced multiple times by the same function. That, in turn, increases the odds of filling up the constant table and running out of slots since we allow only 256 constants in a single chunk.&lt;/p&gt;

&lt;p&gt;Optimize this. How does your optimization affect the performance of the compiler compared to the runtime? Is this the right trade-off?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Proposed Implementation
&lt;/h2&gt;

&lt;p&gt;Recall the contents of the &lt;code&gt;emitIdent&lt;/code&gt; method in the compiler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Short&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeConstant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function currently creates a constant, and returns its index. This index is then used by methods that need the identifier. These are currently &lt;code&gt;let_&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;let_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Expr&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitByte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DefineGlobal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;assign&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;assign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Expr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SetGlobal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and &lt;code&gt;visitVariableExpr&lt;/code&gt;, which currently gets a global value (the only kind of variable currently supported by Matanuska):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;visitVariableExpr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Variable&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ident&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GetGlobal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What this means is that we may create a new method, &lt;code&gt;getIdent&lt;/code&gt;, which wraps &lt;code&gt;emitIdent&lt;/code&gt;, and call that method instead from current users of &lt;code&gt;emitIdent&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What this likely means is storing the ident as a hash key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;IdentTable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="na"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in &lt;code&gt;getIdent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Short&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;undefined&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;variable&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and in &lt;code&gt;emitIdent&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;emitIdent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;Short&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeConstant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;OpCode&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Constant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;idents&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ident&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;constant&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ramifications
&lt;/h2&gt;

&lt;p&gt;On one hand, this will keep the constants table small. In this case, the constants table size would go from 7 values down to 4, a decrease of nearly 50%. Note that, even with the addition of the ident table, memory should still be saved overall, since the identifier would be stored at most twice - once in the constants table, and once in the identifier table.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;compute&lt;/em&gt; cost in the compiler is unclear. On one hand, it would need to check if the identifier is in the table every time &lt;code&gt;getIdent&lt;/code&gt; is called. Additionally, for new identifiers, an additional call would get made to set the ident in the table. However, for reused identifiers, the lookup should be quick, and we &lt;em&gt;avoid&lt;/em&gt; a call to &lt;code&gt;makeConstant&lt;/code&gt;. Without benchmarks, it's likely that this would operate as a very marginal improvement in runtime performance, due to caching.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;runtime&lt;/em&gt; performance would remain largely the same. Array access is roughly O(1), and is unaffected by the size of the constants table. The number of instructions would also remain the same - the calls to the global methods would simply access the same constant.&lt;/p&gt;

&lt;p&gt;It may seem like this change would make it easier to understand the output of a chunk, since identifier constants wouldn't be repeated. However, it would necessarily make the code more complex. Again, it would also have no affect on the disassembled bytecode - it would simply access different constants.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;While this change is tempting, I will not move forward with it at this time. Unlike &lt;code&gt;clox&lt;/code&gt;, our constants table's size is unbound, and it's unclear as to whether or not the potential memory savings would be worth the change.&lt;/p&gt;

&lt;p&gt;In the future, as more complex programs are implemented, we will revisit this question. We will inspect the constants tables for full programs, and make a decision then. For now, we will focus on more important functionality.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
    </item>
    <item>
      <title>Matanuska ADR 018 - Looping Syntax</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Fri, 08 Aug 2025 21:58:35 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-018-looping-syntax-1g49</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-018-looping-syntax-1g49</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;The next feature to implement in Matanuska is looping.&lt;/p&gt;

&lt;p&gt;Classic BASIC's looping structures are... idiosyncratic, especially as compared to what's expected in a modern language. Therefore, we would like to compare what's offered by BASIC versus a modern language (in this case, Python), and make a decision based on these trade-offs.&lt;/p&gt;

&lt;p&gt;It is worth clarifying what design heuristics are important in this decision:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Matanuska BASIC should loosely mirror classic BASIC dialects, such as MSX BASIC.&lt;/li&gt;
&lt;li&gt;Matanuska BASIC should support modern features, such as those in Python, as appropriate.&lt;/li&gt;
&lt;li&gt;Matanuska BASIC should be internally consistent. Design heuristics leveraged in its conditionals should also be reflected in its looping.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Current Behavior
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Blocks
&lt;/h3&gt;

&lt;p&gt;Recall that Matanuska's compiler uses a &lt;code&gt;Block&lt;/code&gt; abstraction to track which block is being compiled. These blocks carry context that's relevant to the block type, and uses a visitor pattern to introduce block-specific behavior for instructions.&lt;/p&gt;

&lt;p&gt;Currently, conditionals are implemented. For am example &lt;code&gt;Block&lt;/code&gt; class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IfBlock&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Block&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;if&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;elseJump&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Short&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;visitElseInstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;else_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Else&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endJump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;else_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elseJump&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;else_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ElseBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endJump&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;visitElseIfInstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elseIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ElseIf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endJump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;else_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elseJump&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;elseJump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;if_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elseIf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;condition&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elseIf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ElseIfBlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;elseJump&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;endJump&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;visitEndIfInstr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_endIf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EndIf&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// TODO: Optimize for no 'else'&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;endJump&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;else_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elseJump&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;endIf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;endJump&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;end&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, these blocks are intended to meet other block-like use cases, such as looping and functions.&lt;/p&gt;

&lt;p&gt;The good news is that, for abstractions which have clear blocks, the abstraction should gracefully carry state at compile time and help ensure that blocks are well-formed. The bad news is that, for things which &lt;em&gt;don't&lt;/em&gt; create blocks but &lt;em&gt;do&lt;/em&gt; create state, that state would need to be tracked separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conditionals
&lt;/h3&gt;

&lt;p&gt;Recall that in &lt;a href="https://dev.toa%20prior%20ADR"&gt;https://dev.to/jfhbrook/matanuska-adr-013-if-then-and-else-syntax-7m6&lt;/a&gt;, we introduced specific syntax for conditionals. Of particular note is that it followed BBC BASIC's lead, and closed multi-line &lt;code&gt;if&lt;/code&gt; blocks with an &lt;code&gt;ENDIF&lt;/code&gt; token. Ideally, we would like to continue those themes here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementations in Other Languages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  MSX BASIC
&lt;/h3&gt;

&lt;p&gt;MSX BASIC, which is typical in terms of a classic BASIC, has only a &lt;code&gt;for/next&lt;/code&gt; loop and the classic &lt;code&gt;goto&lt;/code&gt;. The structure of a classic for/next loop is like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 FOR x%=1 TO 10 STEP 1
20   FOR y%=1 TO 10 STEP 1
30     PRINT x%
40     PRINT y%
30 NEXT x%,y%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that end is inclusive - ie, &lt;code&gt;FOR x=1 TO 10&lt;/code&gt; will print the numbers 1 to 10. Also note that &lt;code&gt;STEP n&lt;/code&gt; and the variables to &lt;code&gt;NEXT&lt;/code&gt; are optional.&lt;/p&gt;

&lt;p&gt;The behavior of &lt;code&gt;NEXT&lt;/code&gt; is a little idiosyncratic - it operates as both an &lt;code&gt;end&lt;/code&gt; and a &lt;code&gt;continue&lt;/code&gt;, and can mark &lt;em&gt;which&lt;/em&gt; loop to continue based on a variable.&lt;/p&gt;

&lt;p&gt;In terms of how this works under the hood, for loops are &lt;strong&gt;not&lt;/strong&gt; structured as blocks as they are in Matanuska. Instead, they are treated as isolated statements which introduce runtime state. Note that this means MSX BASIC can't guard against "broken" nesting, something that Matanuska attempts to do at compile time.&lt;/p&gt;

&lt;p&gt;For completeness, simple GOTO looks like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 PRINT "hello world!"
20 GOTO 10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;GOTO&lt;/code&gt; is very low level, and &lt;em&gt;can not&lt;/em&gt; take advantage of blocks.&lt;/p&gt;

&lt;h3&gt;
  
  
  BBC BASIC
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ NOTICE ⚠️&lt;/p&gt;

&lt;p&gt;This ADR misunderstands the use of the &lt;code&gt;end&lt;/code&gt; keyword in BBC BASIC examples. They keyword is used the same way as &lt;code&gt;end&lt;/code&gt; in MSX BASIC, and are included in the examples to present complete programs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;BBC BASIC's &lt;code&gt;for&lt;/code&gt; loops look a little different:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 FOR x%=1 TO 10
20   PRINT x%
30   NEXT x%
40 END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's noteworthy here is that BBC BASIC uses an &lt;code&gt;END&lt;/code&gt; keyword to close the loop, but also supports &lt;code&gt;NEXT&lt;/code&gt; in a way similar to MSX BASIC. Note that &lt;code&gt;END&lt;/code&gt; is inconsistent with other &lt;code&gt;END{begin_block}&lt;/code&gt; tokens in BBC BASIC.&lt;/p&gt;

&lt;p&gt;Unlike MSX BASIC, BBC BASIC also has &lt;code&gt;while&lt;/code&gt; loops:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHILE x% &amp;gt; 0
  x% = x% / 2
ENDWHILE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing too sophisticated here.&lt;/p&gt;

&lt;p&gt;BBC BASIC also supports a &lt;code&gt;repeat&lt;/code&gt;/&lt;code&gt;until&lt;/code&gt; loop, that is about what one would expect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPEAT
  ...
UNTIL ...
END
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  BASIC8
&lt;/h3&gt;

&lt;p&gt;BASIC8's &lt;code&gt;for&lt;/code&gt; loops are similar to those of MSX BASIC. Its &lt;code&gt;while&lt;/code&gt; loops are similar to BBC BASIC, but use the &lt;code&gt;WEND&lt;/code&gt; keyword instead of the &lt;code&gt;ENDWHILE&lt;/code&gt; keyword.&lt;/p&gt;

&lt;p&gt;It also has a &lt;code&gt;do&lt;/code&gt;/&lt;code&gt;until&lt;/code&gt; loop, similar to BBC BASIC, but with significantly different syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DO
  ...
UNTIL ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unlike BBC BASIC, BASIC8 does not require an &lt;code&gt;END&lt;/code&gt; keyword. It treats &lt;code&gt;UNTIL&lt;/code&gt; as similar to MSX BASIC's &lt;code&gt;NEXT&lt;/code&gt; keyword.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;Python's &lt;code&gt;for&lt;/code&gt; loops look like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's noteworthy here is two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Python takes an iterator as an argument. Classic BASIC does not have first class iterators. But it would be nice if Matanuska could implement them in the future.&lt;/li&gt;
&lt;li&gt;Python has proper blocks, like Matanuska.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Python also has a &lt;code&gt;while&lt;/code&gt; loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also supports the &lt;code&gt;break&lt;/code&gt; and &lt;code&gt;continue&lt;/code&gt; keywords. &lt;code&gt;continue&lt;/code&gt; operates similarly to &lt;code&gt;next&lt;/code&gt; in MSX BASIC. But &lt;code&gt;break&lt;/code&gt; seems novel.&lt;/p&gt;

&lt;h3&gt;
  
  
  JavaScript
&lt;/h3&gt;

&lt;p&gt;JavaScript supports a standard C-like &lt;code&gt;for&lt;/code&gt; loop:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it also supports syntax for iterating over objects, namely arrays:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;xs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For
&lt;/h3&gt;

&lt;p&gt;Standard &lt;code&gt;for&lt;/code&gt; loops will have the following syntax:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 FOR x%=1 TO 10 [STEP 1]
20   PRINT x%
30   NEXT
40 ENDFOR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This leans into the design of BBC BASIC, with a few differences.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;for&lt;/code&gt; loops will be closed with an &lt;code&gt;endfor&lt;/code&gt; keyword. This is inconsistent with BBC BASIC's implementation of &lt;code&gt;for&lt;/code&gt; loops, but consistent with the syntax of its other looping constructs, as well as Matanuska's syntax for conditionals. Additionally, it will allow for clear block semantics.&lt;/p&gt;

&lt;p&gt;Second, &lt;code&gt;NEXT&lt;/code&gt; will be supported optionally in a way that mirrors &lt;code&gt;continue&lt;/code&gt; in Python. However, it will not accept a variable. This is because Matanuska (unlike MSX BASIC) is block structured.&lt;/p&gt;

&lt;p&gt;Note that, in this case, we &lt;em&gt;will&lt;/em&gt; support BASIC's syntax with regard to the &lt;code&gt;TO&lt;/code&gt; keyword. However, in the future, Matanuska will likely support an additional &lt;code&gt;for&lt;/code&gt; loop structure that mirrors &lt;code&gt;for...of&lt;/code&gt; in JavaScript:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10 FOR x% OF xs%
20   ...
30 ENDFOR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The specifics of this syntax and implementation are out of scope for this ADR, and will be revisited when Matanuska BASIC supports arrays.&lt;/p&gt;

&lt;h3&gt;
  
  
  While and Repeat
&lt;/h3&gt;

&lt;p&gt;Matanuska BASIC will also support &lt;code&gt;WHILE&lt;/code&gt;, similar to BBC BASIC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WHILE x% &amp;gt; 0
  x% = x% / 2
ENDWHILE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will also support &lt;code&gt;repeat&lt;/code&gt;/&lt;code&gt;until&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;REPEAT
  ...
UNTIL ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like BASIC8, it will close these blocks with the &lt;code&gt;UNTIL&lt;/code&gt; keyword. Including an &lt;code&gt;ENDREPEAT&lt;/code&gt; keyword would be redundant. However, it will use the &lt;code&gt;REPEAT&lt;/code&gt; keyword in line with BBC BASIC.&lt;/p&gt;

&lt;h3&gt;
  
  
  GOTO
&lt;/h3&gt;

&lt;p&gt;GOTO is considered out of scope for this ADR, and will be revisited at a later date.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
    </item>
    <item>
      <title>Matanuska ADR 017 - Vitest, Vite, Grabthar, Oh My!</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Sun, 09 Feb 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-017-vitest-vite-grabthar-oh-my-10ga</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-017-vitest-vite-grabthar-oh-my-10ga</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In October, what began as a change in test frameworks snowballed into a complete refactor of Matanuska's builds. These changes were very significant, and yet happened quietly. This ADR intends to remedy that situation, to document what those changes were and why they happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tap and NodeNext
&lt;/h2&gt;

&lt;p&gt;When I started Matanuska, I chose &lt;a href="https://node-tap.org/" rel="noopener noreferrer"&gt;Node Tap&lt;/a&gt; as my test framework. Tap was my favorite test framework in Node for a very long time. It's historically had an API that's less "magical" than some of the other frameworks out there, it outputs &lt;a href="https://testanything.org/" rel="noopener noreferrer"&gt;TAP&lt;/a&gt; by default - always a bonus - and it has pretty high-level reporting.&lt;/p&gt;

&lt;p&gt;However, in recent years, Tap has started to grow weary. Its API became more complicated as it needed to support promises and async/await. Its features became more complicated and complex, and it became harder to use.&lt;/p&gt;

&lt;p&gt;But what ultimately made me disillusioned was encountering bugs and odd behavior over time. For instance, I have a directory called &lt;code&gt;./test/helpers&lt;/code&gt; which contains helper modules for my tests. This is a convention I learned from &lt;a href="https://nestjs.com/" rel="noopener noreferrer"&gt;Nest&lt;/a&gt; tests during my time at &lt;a href="https://www.procore.com/" rel="noopener noreferrer"&gt;Procore&lt;/a&gt;. Tap absolutely refused to ignore this directory (which had no tests in it), regardless of my efforts to configure it thusly.&lt;/p&gt;

&lt;p&gt;What pushed me over the edge was issues with native &lt;code&gt;import&lt;/code&gt; syntax in Node.js modules (called "nodenext" in TypeScript parlance). Up to this point, I was using "commonjs" builds, where TypeScript would compile my files to use &lt;code&gt;require&lt;/code&gt;. This was mostly fine and good, but it would struggle with modules using native &lt;code&gt;import&lt;/code&gt;. Most of my dependencies used commonjs, but &lt;a href="https://www.npmjs.com/package/strip-ansi" rel="noopener noreferrer"&gt;one of my development dependencies&lt;/a&gt; was using native import - this in part motivated me to make the switch. Unfortunately, &lt;a href="https://github.com/jfhbrook/matanuska/pull/31" rel="noopener noreferrer"&gt;Tap struggled with this&lt;/a&gt; when I initially made this attempt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vitest
&lt;/h2&gt;

&lt;p&gt;I began searching for a new test framework, and &lt;a href="https://x.com/nucknyan/status/1841666256770105546" rel="noopener noreferrer"&gt;at the recommendation of Nuck&lt;/a&gt;, I gave &lt;a href="https://vitest.dev/" rel="noopener noreferrer"&gt;Vitest&lt;/a&gt; a shot. It's by the developers of &lt;a href="https://vite.dev/" rel="noopener noreferrer"&gt;Vite&lt;/a&gt;, which I loved. I don't do a lot of frontend development, but when I do, Vite is often my choice. Unlike many other solutions to frontend builds I've tried in the past, Vite "just works" and involves minimal baggage (looking at you, Angular).&lt;/p&gt;

&lt;p&gt;It turns out that Vitest is incredible, and I made the change incrementally - but also quickly. I started by configuring Vite to build files named &lt;code&gt;*.spec.ts&lt;/code&gt;, and having Tap run tests named &lt;code&gt;*.tap.ts&lt;/code&gt;. Within a few days, the switch was complete.&lt;/p&gt;

&lt;p&gt;Overall, I have been &lt;em&gt;extremely&lt;/em&gt; happy with Vitest. It has the good parts of Jest and Chai, but without the stranger baggage. It really is incredible, and I can't recommend it enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Native Imports in TSC and SWC
&lt;/h2&gt;

&lt;p&gt;Switching to Vitest fixed native import in the tests, and I was quite happy with that. However, I was not out of the woods when it came to using native import in the main project - that effort was still failing.&lt;/p&gt;

&lt;p&gt;The issue I ran into is deep in the weeds. When in module mode, Node likes to have imports specify the extension of the file you're importing, and doesn't like importing directories - you have to spell out &lt;code&gt;./directory/index.mjs&lt;/code&gt;, rather than simply specifying &lt;code&gt;./directory&lt;/code&gt;. &lt;code&gt;tsc&lt;/code&gt; (&lt;a href="https://www.npmjs.com/package/typescript" rel="noopener noreferrer"&gt;TypeScript's standard compiler&lt;/a&gt;) doesn't rewrite these import paths in "nodenext" mode. This alone made things awkward.&lt;/p&gt;

&lt;p&gt;But I also had my Vitest build configured to use &lt;a href="https://swc.rs/" rel="noopener noreferrer"&gt;SWC&lt;/a&gt;, the compiler backend I had configured for Vitest, and it had issues of its own. SWC is cool. It's a TypeScript compiler written in Rust that is &lt;em&gt;extremely&lt;/em&gt; fast, and - unlike &lt;code&gt;tsc&lt;/code&gt; - it &lt;em&gt;mostly&lt;/em&gt; handles rewriting import paths just fine. However, I did find that it rewrites &lt;code&gt;index.mjs&lt;/code&gt; imports into directory imports.&lt;/p&gt;

&lt;p&gt;I also found that &lt;a href="https://www.npmjs.com/package/@swc/cli" rel="noopener noreferrer"&gt;SWC's standard command line interface&lt;/a&gt; was really immature. This seems to be because it was really intended to run within other build and bundling tools, such as Vite and Nextjs.&lt;/p&gt;

&lt;p&gt;By this point, I was finding the impedance mismatch between Vitest's SWC-based build and my project's &lt;code&gt;tsc&lt;/code&gt; build to be overwhelming, and I yearned to make the two match. I considered switching Vitest to use &lt;code&gt;tsc&lt;/code&gt;. But I also found that SWC was SO much faster (it nearly doubled the speed of my tests) that I couldn't say no. By this point, I was committed to using SWC in my build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vite
&lt;/h2&gt;

&lt;p&gt;I realized that the way to use SWC successfully in Matanuska's main build was going to involve a singular bundle. At this point, I started asking if Vite itself could run my main build. After all, it was building my &lt;em&gt;tests&lt;/em&gt; with SWC successfully!&lt;/p&gt;

&lt;p&gt;As it turns out, Vite is more than capable of doing this, through its &lt;a href="https://vite.dev/guide/ssr" rel="noopener noreferrer"&gt;server-side rendering functionality&lt;/a&gt; (SSR). This is a bit of a misnomer. The &lt;em&gt;motivation&lt;/em&gt; is to support server-side rendering of React projects, but the &lt;em&gt;actual feature&lt;/em&gt; is bundles for server-side JavaScript runtimes like Node.js.&lt;/p&gt;

&lt;p&gt;It's a little limited as compared to its frontend builds - but only a little. For one thing, it can only really handle one SSR entry point. The biggest issue, though, is that Vite's standard dev mode is geared towards &lt;a href="https://vite.dev/guide/api-hmr" rel="noopener noreferrer"&gt;hot module replacement&lt;/a&gt; of frontend code through a proxy over a server - not something that benefits Matanuska. The ramifications of that, though, were simply that I would need to run Vite in batch build mode - not all that different from the pre-existing build process.&lt;/p&gt;

&lt;p&gt;Ultimately, I've been pretty happy with Vite as a build tool. It's blazing fast and does exactly what I need!&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Checking, SWC and TSC
&lt;/h2&gt;

&lt;p&gt;SWC is a great tool when it comes to compiling TypeScript. But it's a &lt;em&gt;bad&lt;/em&gt; tool for &lt;em&gt;type checking&lt;/em&gt; TypeScript. This is because part of why it's so fast is that it mostly ignores types completely. This meant that, while SWC was being used for the builds, I still needed &lt;code&gt;tsc&lt;/code&gt; in the mix for type checking.&lt;/p&gt;

&lt;p&gt;Luckily, &lt;code&gt;tsc&lt;/code&gt; is much more flexible with inputs when it comes to type checking than it is with generating compiled output. After all, it doesn't need to concern itself with output at &lt;em&gt;all&lt;/em&gt; if it's running with the &lt;code&gt;--noEmit&lt;/code&gt; flag.&lt;/p&gt;

&lt;p&gt;Unfortunately, this &lt;em&gt;did&lt;/em&gt; mean that configuration began to sprawl. At this point, I had configurations not just for Vite (shared with Vitest) and &lt;code&gt;tsc&lt;/code&gt;, but also for &lt;a href="https://prettier.io/" rel="noopener noreferrer"&gt;Prettier&lt;/a&gt;, &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;ESLint&lt;/a&gt; and even &lt;a href="https://www.shellcheck.net/" rel="noopener noreferrer"&gt;ShellCheck&lt;/a&gt;. Many of these files had shared settings that needed to match each other. This was somewhat manageable, until Vite was also in the mix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grabthar
&lt;/h2&gt;

&lt;p&gt;My instincts when presented with this configuration sprawl was to begin writing some scripts to generate and update configuration for me. The first draft can be seen in &lt;a href="https://github.com/jfhbrook/matanuska/pull/38" rel="noopener noreferrer"&gt;the PR that initially implemented the Vite build&lt;/a&gt;. These scripts reflected off a shared JSON file (later YAML) and generated the configurations for the downstream tools. In the case of Vite, this happened through an import, but for other tools, it just wrote JSON to disk.&lt;/p&gt;

&lt;p&gt;I began to realize that these scripts were becoming elaborate enough that I wanted to massage them into a proper tool. I created a new package, moved the scripts into it, and named the project &lt;code&gt;grabthar&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This name has a funny background. Many years ago, I was in an IRC conversation with a developer who began describing a build tool he was making. I was a jerk and scoffed at the API, and began sketching out my &lt;em&gt;own&lt;/em&gt; build tool. I named it &lt;code&gt;grabthar&lt;/code&gt; after &lt;a href="https://www.youtube.com/watch?v=kgv7U3GYlDY" rel="noopener noreferrer"&gt;my favorite joke from Galaxy Quest&lt;/a&gt;. It didn't go anywhere, but I kept the source around. When it came time to write a tool for Matanuska, I decided to reuse the name. But anyway, it turns out I was talking to the author of &lt;a href="https://gruntjs.com/" rel="noopener noreferrer"&gt;Grunt&lt;/a&gt;, and boy did I look silly.&lt;/p&gt;

&lt;p&gt;Either way - Matanuska now has a custom build tool. This tool runs hooks to generate configurations, exports functions for tools using JavaScript configs (ie., Vite and ESLint), and runs the appropriate tools in an opinionated manner.&lt;/p&gt;

&lt;p&gt;Make no mistake, &lt;code&gt;grabthar&lt;/code&gt; is &lt;em&gt;extremely&lt;/em&gt; opinionated. Aside from the shared configurations, it's not all that customizable. Any tools using it would need to support &lt;em&gt;exactly&lt;/em&gt; the same underlying build stack as Matanuska. But there are benefits to that, too. I'm currently only using it for Matanuska and a handful of its tools, but may use it outside Matanuska in the future if it ages well.&lt;/p&gt;

&lt;h2&gt;
  
  
  citree
&lt;/h2&gt;

&lt;p&gt;A brief note on &lt;a href="https://github.com/jfhbrook/matanuska/tree/main/packages/citree" rel="noopener noreferrer"&gt;citree&lt;/a&gt;. &lt;code&gt;citree&lt;/code&gt; is a tool I wrote for generating &lt;a href="https://github.com/jfhbrook/matanuska/tree/main/ast" rel="noopener noreferrer"&gt;Matanuska's AST classes&lt;/a&gt;. This tool is heavily inspired by &lt;a href="https://craftinginterpreters.com/representing-code.html#metaprogramming-the-trees" rel="noopener noreferrer"&gt;the script used in Crafting Interpreters' &lt;code&gt;jlox&lt;/code&gt; interpreter&lt;/a&gt;. It uses a DSL implemented in &lt;a href="https://www.npmjs.com/package/typescript-parsec" rel="noopener noreferrer"&gt;ts-parsec&lt;/a&gt; that takes a specification for an AST and generates classes implementing a &lt;a href="https://en.wikipedia.org/wiki/Visitor_pattern" rel="noopener noreferrer"&gt;visitor pattern&lt;/a&gt;. The DSL is a little janky, but it does exactly what I need for Matanuska.&lt;/p&gt;

&lt;p&gt;I considered rewriting &lt;code&gt;citree&lt;/code&gt; to run as a step in the Vite build. However, I decided to keep it as a separate code generation step. This is because, while hacking, I need the TypeScript files to exist in order to do type checking - simple enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  jscc
&lt;/h2&gt;

&lt;p&gt;A final consequence of these refactors was the introduction of &lt;a href="https://www.npmjs.com/package/jscc" rel="noopener noreferrer"&gt;jscc&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Matanuska has included build-time code generation from pretty early on. In particular, it uses an environment variable (&lt;code&gt;MATBAS_BUILD&lt;/code&gt;) to control whether or not to include certain debugging hooks. During development, good debugging output is extremely desirable. But for a release, it slows things down to an unacceptable level - or, at least, that's the common wisdom.&lt;/p&gt;

&lt;p&gt;Initially, I solved this through using &lt;a href="https://www.npmjs.com/package/nunjucks" rel="noopener noreferrer"&gt;nunjucks&lt;/a&gt; templates for a &lt;code&gt;constants.ts&lt;/code&gt; file and a &lt;code&gt;debug.ts&lt;/code&gt; file. Under &lt;code&gt;MATBAS_BUILD=debug&lt;/code&gt;, the latter file would contain debug output, including tracing (&lt;a href="https://dev.to/jfhbrook/matanuska-adr-014-opentelemetry-3ahj"&gt;see ADR 14&lt;/a&gt; for more context here). But under &lt;code&gt;MATBAS_BUILD=release&lt;/code&gt;, those hooks would be empty "no-op" functions. This all worked, but was dissatisfying.&lt;/p&gt;

&lt;p&gt;JSCC was a simple, general purpose tool for the kind of conditional logic I was looking for. Not only did it have a nice syntax that constituted valid JavaScript; it also &lt;a href="https://www.npmjs.com/package/rollup-plugin-jscc" rel="noopener noreferrer"&gt;had a build plugin&lt;/a&gt; that would integrate it into my build for &lt;em&gt;all&lt;/em&gt; my files. To me, this was a major win.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;That was a lot, so I wanted to quickly summarize what happened here.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;citree&lt;/code&gt; for generating the AST&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tsc&lt;/code&gt; for both TypeScript compiling and type checking&lt;/li&gt;
&lt;li&gt;No bundling&lt;/li&gt;
&lt;li&gt;Prettier for formatting&lt;/li&gt;
&lt;li&gt;ESLint for TypeScript linting&lt;/li&gt;
&lt;li&gt;ShellCheck for bash linting&lt;/li&gt;
&lt;li&gt;Node Tap for testing&lt;/li&gt;
&lt;li&gt;Nunjucks for build-time configuration and conditional compiling&lt;/li&gt;
&lt;li&gt;No shared build tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  After
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;citree&lt;/code&gt; for generating the AST, as before&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tsc&lt;/code&gt; for type checking only&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;swc&lt;/code&gt; for TypeScript compiling&lt;/li&gt;
&lt;li&gt;Vite for bundling&lt;/li&gt;
&lt;li&gt;Prettier, ESLint and ShellCheck used for formatting and linting, as before&lt;/li&gt;
&lt;li&gt;Vitest for testing&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;jscc&lt;/code&gt; for build-time configuration and conditional compiling&lt;/li&gt;
&lt;li&gt;Custom &lt;code&gt;grabthar&lt;/code&gt; build tool&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
    </item>
    <item>
      <title>Matanuska ADR 016 - ECMA-55 Compliance</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Wed, 05 Feb 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-016-ecma-55-compliance-250i</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-016-ecma-55-compliance-250i</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;I discovered in my research that there is an actually an old (and largely obsolete) ECMA standard for a minimal BASIC implementation, which can be found here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://ecma-international.org/publications-and-standards/standards/ecma-55/" rel="noopener noreferrer"&gt;https://ecma-international.org/publications-and-standards/standards/ecma-55/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I also discovered two &lt;a href="https://github.com/paladin-t/fantasy" rel="noopener noreferrer"&gt;fantasy console&lt;/a&gt; implementations which discuss adherence to ECMA-55:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://itch.io/t/966540/preliminary-manual" rel="noopener noreferrer"&gt;M16BASIC&lt;/a&gt;, which describes specific deviations from the standard&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jorgicor.niobe.org/bas55/bas55.html" rel="noopener noreferrer"&gt;bas55&lt;/a&gt;, which purports to be spec-compliant and not much else&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Having read the specification - it's about 30 pages - I can say that, generally speaking, it's not worth trying to adhere to in any meaningful way.&lt;/p&gt;

&lt;p&gt;One obvious deficit is that its treatment of variables and values is extremely limiting, and even 80s era BASIC implementations deviated from it significantly. In fact, ECMA-55 only supported strings and numbers, while many other dialects support distinct integers and floats.&lt;/p&gt;

&lt;p&gt;It also, naturally, has specifications for error handling which would be considered odd by today's standards. It doesn't specify exceptions, of course. But, for instance, its &lt;em&gt;recommendation&lt;/em&gt; for integer overflows is to assign an "infinity" value and move forward.&lt;/p&gt;

&lt;p&gt;It does, however, have a few areas worth referencing in the future.&lt;/p&gt;

&lt;h3&gt;
  
  
  Number Literals
&lt;/h3&gt;

&lt;p&gt;The standard includes a specification for how to parse number literals. It only supports decimal representations - no hex or binary - but it does include a specification.&lt;/p&gt;

&lt;p&gt;Matanuska's current behavior isn't specified. The scanner converts strings into numbers with &lt;code&gt;parseInt&lt;/code&gt;, and otherwise appears to follow a subset of a number formatting standard that's compatible with JavaScript. It would be worth considering a specification for numbers.&lt;/p&gt;

&lt;p&gt;Of course, there are many other places to look than ECMA-55. JavaScript's numbers aren't particularly controversial - that specification, or Python's, would suffice. Alternately, VB.NET could be a good source of inspiration.&lt;/p&gt;

&lt;p&gt;It's unlikely that ECMA-55 would play a &lt;em&gt;major&lt;/em&gt; role in informing such a number literal specification. But it could be a useful refrence, and may not be a stretch to support as a subset.&lt;/p&gt;

&lt;h3&gt;
  
  
  Built-In Functions
&lt;/h3&gt;

&lt;p&gt;ECMA-55 includes a very short list of implementation-supplied functions. Supporting these would likely not be difficult, and in fact they may make for a good start.&lt;/p&gt;

&lt;h3&gt;
  
  
  GOTOs
&lt;/h3&gt;

&lt;p&gt;In general, I don't like the semantics of BASIC's GOTOs, and have no interest in adhering to any given standard. That said: the ECMA-55 standard specifies &lt;em&gt;two&lt;/em&gt; keywords (&lt;code&gt;go to&lt;/code&gt;) rather than one (&lt;code&gt;goto&lt;/code&gt;). I don't actually know if that's common in 80s era BASICs. But supporting it wouldn't be difficult either.&lt;/p&gt;

&lt;h3&gt;
  
  
  FOR/NEXT
&lt;/h3&gt;

&lt;p&gt;The standard includes a tight specification for &lt;code&gt;FOR&lt;/code&gt; and &lt;code&gt;NEXT&lt;/code&gt;. The &lt;code&gt;FOR&lt;/code&gt; statement in BASIC is relatively uncontroversial, and I &lt;em&gt;do&lt;/em&gt; intend to develop at least a variant that's more or less a straight cut. It could prove to be a useful reference.&lt;/p&gt;

&lt;h3&gt;
  
  
  DATA, READ and RESTORE
&lt;/h3&gt;

&lt;p&gt;A feature that I've seen in many BASIC implementations, but have had a hard time wrapping my head around, is built around the keywords &lt;code&gt;DATA&lt;/code&gt;, &lt;code&gt;READ&lt;/code&gt; and &lt;code&gt;RESTORE&lt;/code&gt;. Lucky for us, ECMA-55 includes a straightforward explanation of how this works.&lt;/p&gt;

&lt;p&gt;This is important, as BASIC doesn't have syntax for array literals - it uses these keywords to fill in the values of an array instead. While I'm heavily motivated to develop syntax for array literals, these keywords are the likely alternative.&lt;/p&gt;

&lt;h3&gt;
  
  
  PRINT
&lt;/h3&gt;

&lt;p&gt;As discussed in &lt;a href="https://dev.to/jfhbrook/matanuska-adr-015-string-concatenation-operator-4flm"&gt;ADR 15&lt;/a&gt;, the ECMA-55 standard reveals that &lt;code&gt;PRINT&lt;/code&gt;'s behavior is bizarre and strange. When deciding how to implement the &lt;code&gt;PRINT&lt;/code&gt; statement, it will be an invaluable reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;Having read the standard, I do not feel the need to attempt to &lt;em&gt;adhere&lt;/em&gt; to it in any way. By extension, I also do not feel the need to &lt;em&gt;test&lt;/em&gt; for any particular behavior as informed by the specification.&lt;/p&gt;

&lt;p&gt;That said, we &lt;em&gt;have&lt;/em&gt; identified some areas where the standard can operate as a useful &lt;em&gt;reference&lt;/em&gt;. No decisions will be made currently in those identified avenues at this time, but the standard is there when I'm ready.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>standards</category>
    </item>
    <item>
      <title>Matanuska ADR 015 - String Concatenation Operator</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Sun, 02 Feb 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-015-string-concatenation-operator-4flm</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-015-string-concatenation-operator-4flm</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Atto
&lt;/h3&gt;

&lt;p&gt;While researching &lt;a href="https://github.com/paladin-t/fantasy" rel="noopener noreferrer"&gt;fantasy consoles&lt;/a&gt;, I learned about a BASIC implementation called &lt;a href="https://atto.devicefuture.org/" rel="noopener noreferrer"&gt;Atto&lt;/a&gt;. In its &lt;a href="https://github.com/devicefuture/atto/blob/main/docs/frombasic.md" rel="noopener noreferrer"&gt;"from BASIC" doc&lt;/a&gt;, it mentions that it uses &lt;code&gt;;&lt;/code&gt; as its string concatenation operator.&lt;/p&gt;

&lt;p&gt;This seems like an odd decision. But it makes sense when you remember that &lt;code&gt;print&lt;/code&gt; in traditional BASIC use &lt;code&gt;;&lt;/code&gt; to separate expressions. As I understand it, this is ad-hoc syntax in those commands. What Atto does here is take this syntax and &lt;em&gt;generalizes&lt;/em&gt; it. That makes it a &lt;em&gt;very&lt;/em&gt; interesting and clever design choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Matanuska BASIC's Current Behavior
&lt;/h3&gt;

&lt;p&gt;Currently, Matanuska BASIC treats &lt;code&gt;+&lt;/code&gt; as a concatenation operator. This isn't an unusual choice - BASIC8 goes this route, for example. It's also consistent with many modern languages, such as Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Traditional BASIC Behavior
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://ecma-international.org/publications-and-standards/standards/ecma-55/" rel="noopener noreferrer"&gt;ECMA-55 Standard&lt;/a&gt; describes &lt;code&gt;;&lt;/code&gt; as &lt;em&gt;solely&lt;/em&gt; a mechanic within &lt;code&gt;print&lt;/code&gt; statements. Other statements which similarly take multiple arguments use &lt;code&gt;,&lt;/code&gt; to separate them. In fact, &lt;code&gt;print&lt;/code&gt; will allow expressions to be separated by &lt;code&gt;,&lt;/code&gt; as well, but treats &lt;code&gt;,&lt;/code&gt; and &lt;code&gt;;&lt;/code&gt; differently - put a pin in that.&lt;/p&gt;

&lt;p&gt;The specification for &lt;code&gt;PRINT&lt;/code&gt; is... interesting. It specifies fixed-width "zones" for print output within a fixed-width "margin". The specification ensures that numbers can be formatted in a way where they'll always fit within one of these zones. It then specifies that, while &lt;code&gt;;&lt;/code&gt; is treated as a concatenation operator, &lt;code&gt;,&lt;/code&gt; generates enough space to align the following value to the next zone. &lt;code&gt;TAB(&amp;lt;n&amp;gt;)&lt;/code&gt; is treated as &lt;em&gt;special syntax&lt;/em&gt; which "tabs over" &lt;code&gt;n&lt;/code&gt; number of zones. If a print statement &lt;em&gt;ends&lt;/em&gt; in a &lt;code&gt;;&lt;/code&gt; or &lt;code&gt;,&lt;/code&gt;, a newline is &lt;em&gt;not&lt;/em&gt; generated and the &lt;em&gt;following&lt;/em&gt; print statement appends to the end of that line. Finally, if a print statement has enough values that it overflows the margin (or outputs a string which is long enough to do the same), it inserts newlines as-needed to avoid clipping.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;Matanuska will continue to use the &lt;code&gt;+&lt;/code&gt; operator for general purpose string concatenation.&lt;/p&gt;

&lt;p&gt;First, &lt;code&gt;+&lt;/code&gt; is common and well-understood in many modern languages, while &lt;code&gt;;&lt;/code&gt; would be considered strange and unusual. Honestly, that's enough.&lt;/p&gt;

&lt;p&gt;But second, &lt;code&gt;;&lt;/code&gt; isn't treated as a concatenation operator in traditional print statements, as much as it is treated as special syntax for formatting print output - and it's odd syntax at that. Rather than implement &lt;code&gt;;&lt;/code&gt; as a general purpose string concatenation operator, I'd rather leave print statements as a potential odd duck - or possibly avoid traditional semantics altogether, in favor of either &lt;code&gt;echo&lt;/code&gt;-like behavior or shell-like string "templating".&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>strings</category>
    </item>
    <item>
      <title>Matanuska ADR 014 - OpenTelemetry</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Wed, 29 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-014-opentelemetry-3ahj</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-014-opentelemetry-3ahj</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Until recently, I was using a hand-rolled class called &lt;code&gt;Tracer&lt;/code&gt; to attempt to trace execution for debug purposes. It was becoming unwieldy, and I yearned for a better solution. As part of that effort, I implemented OpenTelemetry tracing in debug builds of Matanuska.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Tracing?
&lt;/h3&gt;

&lt;p&gt;Naively, I implemented &lt;code&gt;Tracer&lt;/code&gt; for following the path of execution when errors in parsing or compiling occurred. Both the parser and the compiler have very deep call stacks making stateful changes to the called methods' respective classes. When errors occurred during development, it could be difficult to understand the path of execution that got us there.&lt;/p&gt;

&lt;p&gt;This implies that there's a strong benefit to good visualizations for traces, something my &lt;code&gt;Tracer&lt;/code&gt; class was struggling to provide. By using OpenTelemetry, we can leverage an open source platform, such as &lt;a href="https://www.jaegertracing.io/" rel="noopener noreferrer"&gt;Jaeger&lt;/a&gt;, to view traces.&lt;/p&gt;

&lt;p&gt;In addition, tracing can be valuable for &lt;em&gt;profiling&lt;/em&gt;. While my &lt;code&gt;Tracer&lt;/code&gt; didn't implement it, OpenTelemetry traces are timed, and the visualizations show the relative length of time a given trace took. In a world where I don't have great profiling tools for JavaScript, this could be really valuable.&lt;/p&gt;

&lt;h3&gt;
  
  
  When is Inspecting Better?
&lt;/h3&gt;

&lt;p&gt;A challenge when it comes to tracing is understanding the state of the program at a particular point in time. This can be helped by setting attributes on spans. But in many of these situations, it's arguably more prudent to use Node.js's built-in &lt;a href="https://nodejs.org/en/learn/getting-started/debugging" rel="noopener noreferrer"&gt;inspector support&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The benefits and usage of the inspector functionality are largely out of scope for this discussion. But I bring it up to highlight that tracing is not expected to solve every issue with debugging - at least, on its own. In the future, it will likely operate as complimentary to the inspector.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advantages to OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;Before we get into some of the problems with OpenTelemetry, let's nail down the positives which motivate us to use it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Better visualization and decreased clutter by using a separate GUI backend for viewing traces&lt;/li&gt;
&lt;li&gt;Less maintenance of bespoke abstractions&lt;/li&gt;
&lt;li&gt;Fully-featured API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Issues with OpenTelemetry
&lt;/h3&gt;

&lt;p&gt;There are, unfortunately, a few major issues with OpenTelemetry, which need to either be accepted or mitigated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenging Onboarding
&lt;/h3&gt;

&lt;p&gt;First - and this is the elephant in the room - OpenTelemetry's API is sprawling and the JavaScript documentation is lacking. I think this is for a few reasons:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The API has historically been fast-moving. This meant that, once documentation was written down, it immediately went out of date. The OpenTelemetry developers then struggled to fix it in post, especially as the old APIs weren't deprecated - just lower level than a user might expect.&lt;/li&gt;
&lt;li&gt;The developers are deep subject matter experts on observability, not user experience. This means that, when prompted, they tend to get into the weeds in ways that don't necessarily help a naive user.&lt;/li&gt;
&lt;li&gt;There are generalized problems with tracing that are earnestly hard to solve in a manner conducive to facades and other abstractions. The most significant of these is &lt;a href="https://opentelemetry.io/docs/languages/js/context/" rel="noopener noreferrer"&gt;context management&lt;/a&gt; - that is, how does a trace know which span is its parent? It's tempting to say that if React can hide the complexity of hooks, that they can hide the complexity of contexts as well. But React has the advantage of having a single entry point for execution - team Otel, in contrast, fundamentally has to deal with arbitrary entry points.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This caused significant issues when onboarding. I am, however, hopeful that with internal abstractions and the relative stability of today's OpenTelemetry libraries, that the &lt;em&gt;maintenance&lt;/em&gt; burden will be acceptable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deep Stack Traces
&lt;/h3&gt;

&lt;p&gt;Second - and this was a problem with the hand-rolled abstraction as well - tracing creates very deep stack traces. In the case of OpenTelemetry and &lt;code&gt;trace.startActiveSpan&lt;/code&gt;, it's at least two added layers to the stack - one when calling &lt;code&gt;trace.startActiveSpan&lt;/code&gt; itself, and another when that method calls &lt;code&gt;context.with&lt;/code&gt; in turn. In practice, it's often more.&lt;/p&gt;

&lt;p&gt;This can be somewhat mitigated by calling &lt;code&gt;context.with&lt;/code&gt; directly. But that introduces a lot of boilerplate, in a world where &lt;code&gt;trace.startActiveSpan&lt;/code&gt; is already lightweight enough to motivate a framework-specific implementation with more bells and whistles. Macros could help address the problem, though &lt;code&gt;jscc&lt;/code&gt; doesn't support them as such. But even with macros or boilerplate, it would still add the overhead of &lt;code&gt;context.with&lt;/code&gt;, which is non-optional when using OpenTelemetry.&lt;/p&gt;

&lt;p&gt;Rather than going down this road, here are some other techniques to address the issue:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use &lt;a href="https://opentelemetry.io/docs/languages/js/instrumentation/#span-events" rel="noopener noreferrer"&gt;span events&lt;/a&gt;. These show up as points in time within an owning span, rather than separate spans. Using span events will avoid polluting the stack trace at &lt;em&gt;all&lt;/em&gt;, and are plenty sufficient when the method being called is short-lived - for example, as in most parser methods.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;jscc&lt;/code&gt; to optionally include tracing calls. This can make source mapping more challenging, but will decrease the size of the call stack when tracing isn't desired or necessary.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Sensitivity to Load Ordering
&lt;/h3&gt;

&lt;p&gt;The nature of instrumentation is that it must be loaded &lt;em&gt;as early as possible&lt;/em&gt; in the lifecycle of an application. In fact, it's so sensitive that OpenTelemetry recommends &lt;a href="https://opentelemetry.io/docs/languages/js/getting-started/nodejs/#run-the-instrumented-app" rel="noopener noreferrer"&gt;pushing the setup into a &lt;code&gt;.cjs&lt;/code&gt; file and loading it with the &lt;code&gt;--require&lt;/code&gt; flag from the CLI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In production at many companies, this is simply pulled in as one of the first imports - that was my initial approach as well. But the nature of Matanuska's Vite-based build means that load order can be tough to control.&lt;/p&gt;

&lt;p&gt;The introduction of this &lt;code&gt;--require&lt;/code&gt; flag for debug-only builds means added complexity to the entry point (ie, &lt;code&gt;./bin/matbas&lt;/code&gt;), to the point where templating becomes a reality.&lt;/p&gt;

&lt;h2&gt;
  
  
  On Backends
&lt;/h2&gt;

&lt;p&gt;One challenge introduced by OpenTelemetry was the need for a separate backend service. Luckily, Docker is good at hiding that complexity in a container, and Terraform is good at spinning up and down stacks in a reproducible manner. Jaeger in particular has a &lt;a href="https://www.jaegertracing.io/docs/2.0/getting-started/" rel="noopener noreferrer"&gt;one-container solution&lt;/a&gt; for firing up a backend for local use - this worked great for my purposes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;all that in mind&lt;/strong&gt;, here are the design decisions I went with.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jaeger Backend
&lt;/h3&gt;

&lt;p&gt;I wrote a new tool called &lt;code&gt;fireball&lt;/code&gt; (it's an alcohol joke) which uses Terraform and Docker to stand up and tear down a Jaeger instance. The ergonomics are somewhat similar to Docker Compose - &lt;code&gt;fireball up&lt;/code&gt;, &lt;code&gt;fireball up -d&lt;/code&gt; and &lt;code&gt;fireball down&lt;/code&gt; all work as expected.&lt;/p&gt;

&lt;p&gt;This technique has worked incredibly well, beyond my dreams. It was the least challenging part of the OpenTelemetry implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Telemetry Library
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;--require&lt;/code&gt; technique motivated a separate compiled entry point for the setup of the OpenTelemetry SDK. I decided to push this into a module in a workspace, which is now called via &lt;code&gt;node --require '@matanuska/telemetry' ...&lt;/code&gt;. The major wrinkle, versus the standard Matanuska build, is that I needed to generate a &lt;code&gt;.cjs&lt;/code&gt; build, which in turn meant I needed to use the &lt;code&gt;replace&lt;/code&gt; plugin instead of the &lt;code&gt;consts&lt;/code&gt; plugin (which appears to depend on the use of import syntax). Luckily, this library is simple and these differences are narrowly constrained. It was even able to leverage &lt;code&gt;grabthar&lt;/code&gt;, the custom build tool for Matanuska.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debug Functions
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;debug.ts&lt;/code&gt; module implements thin wrappers around the OpenTelemetry API. In particular, it implements a function called &lt;code&gt;startSpan&lt;/code&gt;, which is somewhat similar to &lt;code&gt;tracer.startActiveSpan&lt;/code&gt; but with the added behaviors of attaching exception data to the span and automatically closing it. In addition, it implements a function called &lt;code&gt;addEvent&lt;/code&gt;, which will fetch the currently active span and add an event to it. These functions are &lt;em&gt;not&lt;/em&gt; hidden behind jscc blocks, as they need to work in the event that they are called in the release build.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jscc Patterns
&lt;/h3&gt;

&lt;p&gt;As mentioned, &lt;code&gt;debug.ts&lt;/code&gt; exposes its helper functions regardless of the value of &lt;code&gt;MATBAS_BUILD&lt;/code&gt;. Instead, this is handled at the call sites.&lt;/p&gt;

&lt;p&gt;First, the functions are conditionally imported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//#if _MATBAS_BUILD == 'debug'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Span&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@opentelemetry/api&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;//#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//#if _MATBAS_BUILD == 'debug'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;startSpan&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./debug&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;//#endif&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The spans are also &lt;em&gt;called&lt;/em&gt; conditionally. For instance, in the main loop of the REPL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;repl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Executor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;//#if _MATBAS_BUILD == 'debug'&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;startSpan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;read-eval-print&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Span&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;//#endif&lt;/span&gt;
      &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;executor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;BaseFault&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Exit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;BaseException&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;RuntimeFault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;//#if _MATBAS_BUILD == 'debug'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;//#endif&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This helps avoid the performance overhead inherent in adding tracing, and additionally may help simplify stack traces for release build errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Entrypoint
&lt;/h3&gt;

&lt;p&gt;The entrypoint is now assembled from templates, using Terraform. This is accomplished with another module, similar in interface to &lt;code&gt;fireball&lt;/code&gt; or &lt;code&gt;citree&lt;/code&gt;. It is called during Matanuska's build process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Loose Ends
&lt;/h2&gt;

&lt;p&gt;This work lays the foundations for doing some really cool things with tracing. However, it does leave some loose ends.&lt;/p&gt;

&lt;h3&gt;
  
  
  Incomplete Instrumentation
&lt;/h3&gt;

&lt;p&gt;First, at the time of this writing, the parser and compiler are not fully instrumented. Spans for large parts of Matanuska are in-place, but the end result is fundamentally not as fine-grained as it was previously. This is intentional, even aside from efforts to cap scope. As mentioned, it's unclear how useful traces are for fine-grained debugging as compared to inspector tools. As such, I decided to fully instrument these components on an as-needed basis. That will hopefully lead to more useful traces long term.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bugs
&lt;/h3&gt;

&lt;p&gt;Second, there are number of small but significant bugs. One of them is that the root span for the REPL appears to log twice within Jaeger, for unknown reasons. Another is that spans for script runs are never received at all, likely due to the process exiting before spans can be flushed. OpenTelemetry was largely assumed to be running in the context of long-running applications, and those assumptions are running counter to my current usage. These weren't considered enough to stop me from shipping, but should be addressed eventually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jscc Patterns for Non-Telemetry Use Cases
&lt;/h3&gt;

&lt;p&gt;This ADR outlines a series of patterns for use of the &lt;code&gt;debug&lt;/code&gt; module. This pattern is new - older features are given default no-op implementations in the &lt;code&gt;debug&lt;/code&gt; module. This is because, historically, the problems solved by &lt;code&gt;jscc&lt;/code&gt; in all files were being handled through templating in just the &lt;code&gt;debug&lt;/code&gt; module. The work to migrate other uses of this module to follow the same patterns is outstanding, as is an ADR discussing how &lt;code&gt;jscc&lt;/code&gt; came into the picture.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debug Logging
&lt;/h3&gt;

&lt;p&gt;Third, there are some questions around debug logging. By default, the SDK will create a &lt;code&gt;DiagLogger&lt;/code&gt; if the &lt;code&gt;OTEL_LOG_LEVEL&lt;/code&gt; environment variable is set to a valid value, such as &lt;code&gt;info&lt;/code&gt;. However, the output is really basic and does not spark joy.&lt;/p&gt;

&lt;p&gt;Related is logging from Nestjs, our dependency injection framework. Prior to these changes, I was using functionality on the tracer to log debug information from Nestjs. Now that this is gone, I'm doing so with the default Nest logger.&lt;/p&gt;

&lt;p&gt;Between these two, it's tempting to create some shared logging conventions between both Nest and &lt;code&gt;@matanuska/telemetry&lt;/code&gt;. However, &lt;code&gt;@matanuska/telemetry&lt;/code&gt; should not have a &lt;em&gt;dependency&lt;/em&gt; on a given logging library - the implementation should be inline as to avoid instrumentation ordering bugs. Moreover, the &lt;code&gt;OTEL_LOG_LEVEL&lt;/code&gt; behavior is relatively difficult to customize and override.&lt;/p&gt;

&lt;h3&gt;
  
  
  Debugging and Inspecting
&lt;/h3&gt;

&lt;p&gt;Finally, I want to reiterate that the jury is still out in terms of whether or when debugging will be more useful. I deeply suspect that I'll find the debugger more useful in many of the situations I was trying to solve with my &lt;code&gt;Tracer&lt;/code&gt; previously. But until I actually have those issues and attempt to use Node's debugging facilities, it will be a mystery. For now, I'm going to include telemetry as a feature, and see where it leads.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
    </item>
    <item>
      <title>Matanuska ADR 013 - If, Then and Else Syntax</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Sun, 26 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-013-if-then-and-else-syntax-7m6</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-013-if-then-and-else-syntax-7m6</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In order to implement conditional branching, we need to specify a syntax for if, then and else. We would like this syntax to match the expectations of a period-appropriate BASIC, but we would also like it to support modern idioms, particularly multi-line branches.&lt;/p&gt;

&lt;p&gt;MSX BASIC supports the following forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;IF &amp;lt;condition&amp;gt; THEN &amp;lt;lineNo | instructions&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IF &amp;lt;condition&amp;gt; THEN &amp;lt;lineNo | instructions&amp;gt; ELSE &amp;lt;lineNo | instructions&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IF &amp;lt;condition&amp;gt; GOTO &amp;lt;lineNo&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;IF &amp;lt;condition&amp;gt; GOTO &amp;lt;lineNo&amp;gt; ELSE &amp;lt;lineNo | instructions&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When supplied a lineNo, BASIC will jump to that line. Otherwise, it will run the listed instructions.&lt;/p&gt;

&lt;p&gt;Notable is that MSX BASIC does &lt;em&gt;not&lt;/em&gt; support multi-line if statements. For an example of a BASIC which &lt;em&gt;does&lt;/em&gt; support them, we can look to BBC BASIC. BBC BASIC supports the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IF &amp;lt;condition&amp;gt; THEN
  ...
ELSE
  ...
ENDIF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also notable is that neither MSX BASIC nor BBC BASIC has an &lt;code&gt;else if&lt;/code&gt; - though, MSX BASIC likely parses &lt;code&gt;if &amp;lt;cond&amp;gt; then &amp;lt;ins&amp;gt; else if &amp;lt;cond&amp;gt; then &amp;lt;ins&amp;gt; else &amp;lt;ins&amp;gt;&lt;/code&gt; as &lt;code&gt;If(cond, ins, If(cond, ins, ins))&lt;/code&gt;, where the second &lt;code&gt;if&lt;/code&gt; is simply another instruction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;We will support two kinds of "if"s: single-line instructions (called &lt;code&gt;ShortIf&lt;/code&gt; in the AST) and multi-line collections of instructions (made up of the instructions &lt;code&gt;If&lt;/code&gt;, &lt;code&gt;ElseIf&lt;/code&gt;, &lt;code&gt;Else&lt;/code&gt; and &lt;code&gt;EndIf&lt;/code&gt;. Single-line if instructions may be evaluated as commands.&lt;/p&gt;

&lt;p&gt;Single-line if statements will initially support the following forms:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if &amp;lt;condition&amp;gt; then &amp;lt;instructions&amp;gt; endif&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if &amp;lt;condition&amp;gt; then &amp;lt;instructions&amp;gt; else &amp;lt;instructions&amp;gt; endif&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In these forms, &lt;code&gt;instructions&lt;/code&gt; may not contain if/else/endif instructions used in a multi-line context. They may, however, support nested short ifs - that is, &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;else&lt;/code&gt; are allowed if they're closed with an &lt;code&gt;endif&lt;/code&gt; on the same line.&lt;/p&gt;

&lt;p&gt;Multi-line if blocks will support forms such as the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;line_no&amp;gt; if &amp;lt;condition&amp;gt; then
  &amp;lt;lines&amp;gt;
&amp;lt;line_now&amp;gt; else if &amp;lt;condition&amp;gt; then
  &amp;lt;lines&amp;gt;
&amp;lt;line_no&amp;gt; else
  &amp;lt;lines&amp;gt;
&amp;lt;line_no&amp;gt; endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;else if&lt;/code&gt; will be treated the same as in JavaScript, or like &lt;code&gt;elif&lt;/code&gt; in Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Not Supported
&lt;/h3&gt;

&lt;h4&gt;
  
  
  GOTOs
&lt;/h4&gt;

&lt;p&gt;These forms don't support line numbers for &lt;code&gt;goto&lt;/code&gt;. That decision will be made in the future, when &lt;code&gt;goto&lt;/code&gt; is existing functionality.&lt;/p&gt;

&lt;h4&gt;
  
  
  Else If in Short If
&lt;/h4&gt;

&lt;p&gt;As noted, "long if" supports &lt;code&gt;else if&lt;/code&gt;. However, "short if" currently parses &lt;code&gt;if &amp;lt;cond_a&amp;gt; then &amp;lt;then_a&amp;gt; else if &amp;lt;cond_b&amp;gt; then &amp;lt;then_b&amp;gt; endif endif&lt;/code&gt; as containing a nested "short if" within an "else" block, and &lt;code&gt;if &amp;lt;cond_a&amp;gt; then &amp;lt;then_a&amp;gt; else if &amp;lt;cond_b&amp;gt; then &amp;lt;then_b&amp;gt; endif&lt;/code&gt; is considered unterminated. This is because the whitespace in a "long if" is significant!&lt;/p&gt;

&lt;p&gt;This issue reveals a wart in Matanuska BASIC's syntax - and, in fact, BBC BASIC does not support an analogous construction.&lt;/p&gt;

&lt;p&gt;One way to address this may be to introduce a new keyword, &lt;code&gt;elif&lt;/code&gt;, that operates like the corresponding keyword in Python. In fact, if that were the case, we would likely deprecate &lt;code&gt;else if&lt;/code&gt; in long ifs in favor of &lt;code&gt;elif&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There's an argument to be made for not supporting "else if" in Matanuska BASIC at this time, in order to avoid such a deprecation. That would be consistent with other decisions made in this ADR. However, unlike those decisions, this one doesn't significantly complicate the parser. Moreover, there isn't a strong motivation to use &lt;code&gt;else if&lt;/code&gt; on its own line to represent a discrete &lt;code&gt;if&lt;/code&gt; inside a &lt;code&gt;else&lt;/code&gt; block, therefore deprecation is anticipation of such a deprecation. That would not expected to be painful.&lt;/p&gt;

&lt;h4&gt;
  
  
  Unterminated "Short If" and Multi-Line with Then on Same Line
&lt;/h4&gt;

&lt;p&gt;Also not supported are an unterminated short if, as in MSX BASIC and BBC BASIC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if &amp;lt;condition&amp;gt; then &amp;lt;instructions&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and a long if supporting "then" instructions on the first line, which is not supported by MSX BASIC nor BBC BASIC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;line_no&amp;gt; if &amp;lt;condition&amp;gt; then &amp;lt;instructions&amp;gt;
&amp;lt;line_no&amp;gt;   else &amp;lt;instructions&amp;gt; endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allowing one of these forms doesn't strictly rule out the other. But supporting both requires a more complicated grammar. For instance, an implementation supporting both may require lookahead in the compiler, or possibly backtracking in the parser.&lt;/p&gt;

&lt;p&gt;Allowing either form exclusively, on the other hand, is relatively straightforward. This means that, while implementing one of them would be easy, it would make it much more difficult to implement the other.&lt;/p&gt;

&lt;p&gt;By implementing neither, we leave the door open on this issue.&lt;/p&gt;

&lt;h4&gt;
  
  
  Then on Following Line
&lt;/h4&gt;

&lt;p&gt;Finally, a feature for which support was considered but dropped is starting &lt;code&gt;then&lt;/code&gt; on the following line, like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;line_no&amp;gt; if &amp;lt;condition&amp;gt;
&amp;lt;line_no&amp;gt;   then &amp;lt;instructions&amp;gt;
  &amp;lt;lines&amp;gt;
&amp;lt;line_now&amp;gt; else if &amp;lt;condition&amp;gt;
&amp;lt;line_no&amp;gt;    then &amp;lt;instructions&amp;gt;
  &amp;lt;lines&amp;gt;
&amp;lt;line_no&amp;gt; else
  &amp;lt;lines&amp;gt;
&amp;lt;line_no&amp;gt; endif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are aesthetic reasons to support this form. However, allowing it also complicates the parser by introducing a new form for lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;line_with_then := &amp;lt;line_no&amp;gt; then &amp;lt;instructions&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with this form only being valid if the &lt;em&gt;previous&lt;/em&gt; line contains an &lt;code&gt;if&lt;/code&gt; statement ending before the &lt;code&gt;then&lt;/code&gt;. Practically speaking, parsing this form requires maintaining an extra piece of state in the parser - "should we expect a &lt;code&gt;then&lt;/code&gt;" - and matching it prior to parsing other instructions in those cases. This isn't a heavy lift, but it's &lt;em&gt;enough&lt;/em&gt; of a complication that BBC BASIC did not implement it. In our case, we're deciding to leave it out &lt;em&gt;for now&lt;/em&gt;, so as to not immediately commit to the additional complexity in our parser. It may, however, be introduced in the future.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>syntax</category>
    </item>
    <item>
      <title>Matanuska ADR 012 - Execution Domain Model</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Wed, 22 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-012-execution-domain-model-oef</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-012-execution-domain-model-oef</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;Matanuska contains a number of domain concepts to describe the syntax and semantics of source code, parsed trees, and compiled programs. Currently, they are named (and defined) as such:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Expressions&lt;/strong&gt;: Units of code which apply a stack of &lt;em&gt;operations&lt;/em&gt; on &lt;em&gt;values&lt;/em&gt; and evaluate to another value. Expressions do not cause side effects directly, though function calls may execute &lt;em&gt;commands&lt;/em&gt; which &lt;em&gt;do&lt;/em&gt; cause side effects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Values&lt;/strong&gt;: Elements within memory that represent numbers, booleans, strings, and various object types. May either be contained within a variable, or specified as a "literal" within source code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations&lt;/strong&gt; and &lt;strong&gt;Operators&lt;/strong&gt; (&lt;strong&gt;source&lt;/strong&gt; and &lt;strong&gt;AST&lt;/strong&gt;): Elements within &lt;em&gt;expressions&lt;/em&gt; that add or remove &lt;em&gt;values&lt;/em&gt; from the expression stack when they're evaluated. These have varying semantics, such as having infix/prefix/postfix operators and having operator precedence. Note that &lt;em&gt;operators&lt;/em&gt; are the syntactic element, and &lt;em&gt;operations&lt;/em&gt; are the corresponding units of execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt;: Units of code which generally invoke state when executed. Commands &lt;em&gt;may&lt;/em&gt; take expressions as arguments, but may also include non-expression syntactic elements or other commands. These are generally separated by colons (&lt;code&gt;:&lt;/code&gt;) within &lt;em&gt;command groups&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interactive commands&lt;/strong&gt;: Commands which may only be executed in an interactive session, through use of the &lt;code&gt;command&lt;/code&gt; module. These commands are syntactically within non-line &lt;em&gt;command groups&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime commands&lt;/strong&gt;: Commands which are compiled into chunks and op codes and executed by the &lt;code&gt;runtime&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt; and &lt;strong&gt;complex&lt;/strong&gt; commands: Complex commands are commands which &lt;em&gt;may&lt;/em&gt; contain other commands. This is in contrast to "simple commands", which &lt;em&gt;never&lt;/em&gt; contain other commands. This concept is not currently represented within the source code, but is useful to distinguish in the context of this ADR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Command groups&lt;/strong&gt;: A series of commands, separated by colons (&lt;code&gt;:&lt;/code&gt;). These command groups may form the non-numbered portion of a line, or may constitute "bare" &lt;em&gt;interactive commands&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lines&lt;/strong&gt;: A command group combined with a preceding line number. Lines are currently contained on a single source line, though that may not be true in the future if newlines can be escaped (like Bash).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instructions&lt;/strong&gt;: Collections of &lt;em&gt;op codes&lt;/em&gt; and &lt;em&gt;addresses&lt;/em&gt; which, when encountered by the runtime, cause some effect to occur.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Op codes&lt;/strong&gt;: Integer values which, when combined with addresses into &lt;em&gt;instructions&lt;/em&gt;, cause the &lt;code&gt;runtime&lt;/code&gt; to execute an "operation". These values are between 0 and 255, and are intended to be represented by bytes (as in bytecode). These are also called "byte codes" - or simply "codes". "Op" is short for "operation".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operations&lt;/strong&gt; (&lt;strong&gt;bytecode&lt;/strong&gt;): Stateful actions which occur when the &lt;code&gt;runtime&lt;/code&gt; encounters an &lt;em&gt;op code&lt;/em&gt;. This is distinct from &lt;em&gt;operations&lt;/em&gt; in source or AST code, though they may implement the behavior implied by a source or AST operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Chunks&lt;/strong&gt;: Collections of &lt;em&gt;bytecode&lt;/em&gt;, along with various pieces of metadata. These form the base units which are executed by the &lt;code&gt;runtime&lt;/code&gt;. This term comes directly from &lt;code&gt;Crafting Interpreters&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bytecode&lt;/strong&gt;: The unifying concept around &lt;em&gt;op codes&lt;/em&gt;, &lt;em&gt;instructions&lt;/em&gt; and &lt;em&gt;chunks&lt;/em&gt;. In other words, &lt;em&gt;bytecode&lt;/em&gt; is made up of &lt;em&gt;chunks&lt;/em&gt; and &lt;em&gt;instructions&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, in the &lt;a href="https://www.msx.org/wiki/Category:MSX-BASIC_Instructions" rel="noopener noreferrer"&gt;MSX Wiki&lt;/a&gt;, they very consistently call them "instructions" - and it is believed that the domain model in not &lt;em&gt;just&lt;/em&gt; MSX BASIC but most other classic BASIC languages as well.&lt;/p&gt;

&lt;p&gt;This raises the question: should Matanuska rename "commands" to "instructions"? If so, how would this decision cascade to the rest of the domain model?&lt;/p&gt;

&lt;h2&gt;
  
  
  Motivations for Renaming Commands
&lt;/h2&gt;

&lt;p&gt;The first, and most obvious reason, to rename "commands" to "instructions" is to maintain consistency with MSX BASIC. While this may allow for borrowing more ideas from MSX BASIC (instead of having to invent them), it &lt;em&gt;also&lt;/em&gt; makes Matanuska more of a "true BASIC". One of Matanuska's design goals is to, in fact, invoke classic elements of an 80s era BASIC; therefore, this reason is compelling.&lt;/p&gt;

&lt;p&gt;But another motivation is that the term "command" is overloaded in Matanuska right now. This can be seen by having to differentiate between "interactive commands" and "runtime commands". While these types of commands are represented similarly in source code, they are executed through very different mechanics. In the former case, they are executed through the &lt;code&gt;command&lt;/code&gt; module directly from the AST. In the latter, they are compiled into "instructions" or "op codes" and executed through the &lt;code&gt;runtime&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This could also be motivated by a desire to align with &lt;code&gt;WIC&amp;amp;I&lt;/code&gt;. The idea of a separate execution path for commands, through the &lt;code&gt;command&lt;/code&gt; module, comes directly from this source, which implies that "runtime commands" are not commands in the &lt;code&gt;WIC&amp;amp;I&lt;/code&gt; model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instructions in ASTs and Bytecode
&lt;/h2&gt;

&lt;p&gt;Consider that we rename "commands" to "instructions", and consider "interactive commands" to be simply "commands", a special case of "instructions". This reveals a new problem: the term "instruction" is already used within the &lt;code&gt;runtime&lt;/code&gt; to refer to collections of op codes and addresses.&lt;/p&gt;

&lt;p&gt;This is not entirely accidental - in traditional BASIC, programs are stored in an uncanny valley between AST and bytecode. Like bytecode, BASIC programs are stored in bytes, with instructions stored in "reverse polish" - but, like an AST, they can be translated back into source code at any time. The structure of BASIC means that source code instructions are exactly equivalent to bytecode instructions. Even as Matanuska diverges from this design, there's still a clear lineage of semantics between the construction of a BASIC source instruction and a bytecode instruction.&lt;/p&gt;

&lt;p&gt;On the surface, it may make sense to rename "instructions" in bytecode to some alternative name. However, that's challenging, because "instruction" has particular meaning within the context of bytecode. But consider that "instruction" has general meaning within a language itself, and that - in a sense - the BASIC source and the bytecode constitute different &lt;em&gt;languages&lt;/em&gt;. In short, "instruction" has the same semantic meaning with the additional context of them being in the source code and AST or in bytecode. This is different from the case of "command", as "interactive commands" are a subtype of source/AST instructions/commands, rather than a type of command distinct from source/AST instructions/commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;Matanuska will modify its definitions for execution domain concepts to align with the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Instructions&lt;/strong&gt; (&lt;strong&gt;Source&lt;/strong&gt; or &lt;strong&gt;AST&lt;/strong&gt;): Units of code which generally invoke state when executed. These were previously called &lt;em&gt;commands&lt;/em&gt;. This will extend to other concepts: "instruction groups", "simple instructions", "complex instructions", etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Commands&lt;/strong&gt;: Instructions which are executed in an interactive session. This is similar to the previous idea of &lt;em&gt;interactive commands&lt;/em&gt;, but also includes "runtime commands" which were executed interactively. Note that, depending on the context, a command &lt;em&gt;may&lt;/em&gt; be made up of multiple colon-separated instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime instructions&lt;/strong&gt;: Instructions which are compiled into chunks and codes and executed by the &lt;code&gt;runtime&lt;/code&gt;. These were previously called "runtime commands". Often these will simply be called "instructions" - we only need to invoke "runtime instructions" when there's a strong need to exclude "commands".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime commands&lt;/strong&gt; (or &lt;strong&gt;compiled commands&lt;/strong&gt;): Runtime instructions which were sent as commands in the REPL. These are known as "commands" before they are scanned and sorted into runtime instructions, at which point they are known as "runtime commands" or "instructions" depending on the context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instructions&lt;/strong&gt; (&lt;strong&gt;Bytecode&lt;/strong&gt;): Instructions in bytecode will continue to be called "instructions", but may be referred to as "bytecode instructions" to distinguish from source or AST instructions.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>domains</category>
    </item>
    <item>
      <title>Matanuska ADR 011 - Let and Assign Semantics</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Sun, 19 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-011-let-and-assign-semantics-3pi5</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-011-let-and-assign-semantics-3pi5</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In a traditional BASIC, the &lt;code&gt;let&lt;/code&gt; keyword is used for both defining variables and assigning new values to them. But this keyword is also optional - most BASIC interpreters understand &lt;code&gt;i% = 1&lt;/code&gt; and &lt;code&gt;let i% = 1&lt;/code&gt; to be synonymous.&lt;/p&gt;

&lt;p&gt;On the other hand, many modern languages have semantic division between definition and assignment. For example, JavaScript defines variables with &lt;code&gt;let&lt;/code&gt; or &lt;code&gt;const&lt;/code&gt;, and uses a simple &lt;code&gt;=&lt;/code&gt; for assigning to existing variables. This allows JavaScript to distinguish between local and non-local (or global) variables.&lt;/p&gt;

&lt;p&gt;Python handles this in reverse - a simple &lt;code&gt;=&lt;/code&gt; will define a local variable if it's unassigned, but the &lt;code&gt;nonlocal&lt;/code&gt; and &lt;code&gt;global&lt;/code&gt; keywords will allow for assigning to those kinds of variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;In Matanuska, &lt;code&gt;let&lt;/code&gt; will be used for defining variables, and a simple &lt;code&gt;=&lt;/code&gt; will be used for assignment. This will allow supporting non-local variables in the future, while having syntax and idioms similar to a traditional BASIC.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
    </item>
    <item>
      <title>Matanuska ADR 010 - Architecture, Revisited</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Wed, 15 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-010-architecture-revisited-2bc6</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-010-architecture-revisited-2bc6</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jfhbrook/matanuska-adr-002-architecture-53b0"&gt;ADR 002&lt;/a&gt;, we outlined an initial architecture based on the book &lt;code&gt;Writing Interactive Compilers &amp;amp; Interpreters&lt;/code&gt; by PJ Brown. But as implementing Matanuska has progressed, the actual architecture has deviated from that design. This document details these changes, along with their rationale and consequences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture v1
&lt;/h3&gt;

&lt;p&gt;Let's start with a diagram of the architecture prior to significant refactors away from the design in ADR 002:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oaxvwxjd0h21w205rfx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6oaxvwxjd0h21w205rfx.png" alt="Matanuska BASIC architecture v1" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this architecture, that Translator is responsible for reading input from either an interactive session or a script, parsing it, adding lines to the editor, and passing parsed ASTs to the Commander. The commander, meanwhile, is in charge of both compiling and executing runtime commands, &lt;em&gt;and&lt;/em&gt; directly executing non-runtime interactive commands (such as editing). The editor is referenced by both the Translator and the Commander - the former writes to it, and the latter reads from it. Finally, the initial architecture called for a "recreator", which would take parsed ASTs and convert them back to source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;The architecture after significant refactors looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z713ey398di267ukyb8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1z713ey398di267ukyb8.png" alt="Matanuska BASIC architecture v2" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I will now dive into the differences between this architecture and the previous architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Executor and the Command Module
&lt;/h3&gt;

&lt;p&gt;First, the Commander has been renamed to Executor, though it retains much of the responsibilities of the "command module" called for by WIC&amp;amp;I. Second, a second command subsystem (literally in the "commands" module) is in charge of interpreting interactive commands through a visitor pattern - though, crucially, it calls back to the Executor to do the heavy lifting. While the command module implements high-level flows, the Executor remains at the core of executing commands.&lt;/p&gt;

&lt;p&gt;The new command module is motivated by the use of a visitor pattern. While I don't think it's entirely accurate to say nobody was doing object oriented programming in the age of PJ Brown, I &lt;em&gt;do&lt;/em&gt; think it was uncommon, and the book doesn't indicate the use of any particular patterns. Chief among these is the visitor pattern, as detailed in &lt;code&gt;Crafting Interpreters&lt;/code&gt; by Robert Nystrom. Matanuska, in contrast to the implied WIC&amp;amp;I architecture, uses the visitor pattern extensively.&lt;/p&gt;

&lt;p&gt;The visitor pattern ends up being a good way to avoid large if/else statements doing &lt;code&gt;instanceof&lt;/code&gt; checks on command objects. Instead, it tells a given command object to delegate behavior to the appropriate method on a visitor. Initially, the Commander was intended to be that visitor, but it made sense to separate that visitor from session management and execution - hence, the separation of responsibilities.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Translator and the REPL
&lt;/h3&gt;

&lt;p&gt;This revised architecture does away with the Translator, moves parsing and editing responsibilities to the Executor, and leaves behind two functions in the index module: &lt;code&gt;script&lt;/code&gt;, which tells the Executor to load and run a script, and &lt;code&gt;repl&lt;/code&gt;, which implements a simple read-eval-print loop (REPL) on top of the Executor.&lt;/p&gt;

&lt;p&gt;The primary driver for this change is pressure to move parsing and editing to the Executor. Prior to this change, input was being interpreted in two layers - once to decide whether input should be put in the Editor or passed to the Commander; and second in the Commander itself.&lt;/p&gt;

&lt;p&gt;This created two problems. The first was that script and interactive execution became inconsistent. The former calls &lt;code&gt;load()&lt;/code&gt; and &lt;code&gt;run()&lt;/code&gt; on the Commander, which then need to call the parser (or the translator as a proxy); while the latter needed to do parsing in order to know if input was destined for the Editor or not.&lt;/p&gt;

&lt;p&gt;Additionally, this meant that the Editor was being accessed by two different objects, effectively using it to pass data to each other. This created an awkward division of concerns problem. Ideally, the responsibility of delegating edits would remain with one class, not two.&lt;/p&gt;

&lt;p&gt;The idea of a "translator" also seems to combine both the responsibilities of a parser and a REPL.&lt;/p&gt;

&lt;p&gt;The concept of a REPL &lt;a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop" rel="noopener noreferrer"&gt;came from Lisp&lt;/a&gt;, with origins in 1964 but popularized with Scheme in the 80s. I don't have strong evidence of this, but I suspect the terminology (and implied architecture) wasn't common outside Lisp until non-BASIC, non-shell scripting languages gained popularity in the 1990s; and the Open Source Movement (particularly as related to GNU) spread the gospel of Lisp. I suspect that the advice for implementing a translator is simply out of date.&lt;/p&gt;

&lt;p&gt;Moreover, the v1 architecture already provided a separate parser abstraction. This made the translator, at best, a proxy for the parser that also included the baggage of a REPL.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Interactive Compiler
&lt;/h3&gt;

&lt;p&gt;The compiler module was refactored to handle both interactive and runtime commands, and return different results accordingly. Without this change, the Commander had to separate commands within input and implement switching logic internally. This sounds like a minor issue. But it created challenges when trying to report compiler warning all at once, rather than piecemeal as commands were executed. This change allows all compilation and warning collection to occur in a single pass.&lt;/p&gt;

&lt;p&gt;It's also motivated by yet another visitor. The runtime compiler uses a visitor to convert commands into Chunks, while the interactive compiler decides whether or not a command is interactive or not. This is distinct from the visitor that takes an already-known interactive command and decides how to execute it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Recreator
&lt;/h3&gt;

&lt;p&gt;Finally, the recreator was removed from the architecture. This is because, as the AST evolved, it ended up retaining the full source code for each line. This was motivated by good error messages - when an error is found during parsing or compiling, error messages are able to take both the original source and offsets to display exactly where the error was located.&lt;/p&gt;

&lt;p&gt;Given that parsed lines retain their original source, the recreator was no longer required - hence, removed. Something &lt;em&gt;like&lt;/em&gt; the recreator may end up being implemented in the future - however, it would operate less as a requirement for generating listings, and more as a code formatter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Future Concerns
&lt;/h2&gt;

&lt;p&gt;In the future, I can see a few other changes being made.&lt;/p&gt;

&lt;p&gt;First is revisiting ownership of readline management. This is currently handled by the Executor. But it's also squarely an I/O concern, and all other I/O and OS operations are currently handled by the Host. One could argue that readline is "higher level" than the functionality of the Host, or that it's appropriate due to the Executor's ownership of sessions. But I'm not entirely convinced.&lt;/p&gt;

&lt;p&gt;Second is revisiting the decision to make the Host a pluggable component in charge of I/O. The surface area of Host is &lt;em&gt;huge&lt;/em&gt;. Meanwhile, Node.js - and other potential encoding language targets - have perfectly capable OS and FS modules. There &lt;em&gt;is&lt;/em&gt; a need to wrap these capabilities, namely for reifying errors into Exceptions. But there are other ways of doing this. For example, Matanuska could implement OS and FS modules of its own.&lt;/p&gt;

&lt;p&gt;A major motivator for a Host module was to make it pluggable. The ideas was that the implementation of Matanuska would be independent of the JavaScript runtime - for example, in the browser, one could use a BrowserHost instead of a ConsoleHost. In fact, this is used for testing, where we implement a MockConsoleHost. But there are other ways to mock these libraries. All that said, we will stay the course for now.&lt;/p&gt;

&lt;p&gt;Finally, it may make sense to separate session management from the Executor. The executor has already been separated from command interpreting concerns, which leaves it with execution logic and session management - arguably two easily separable responsibilities. The executor is currently reasonably sized and comfortably owns prompting, so it's OK to keep these responsibilities coupled for now. But if the Executor gets too large, this may end up being a refactor worth considering.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Matanuska ADR 009 - Type Awareness in The Compiler and Runtime</title>
      <dc:creator>Josh Holbrook</dc:creator>
      <pubDate>Sun, 12 Jan 2025 12:00:00 +0000</pubDate>
      <link>https://dev.to/jfhbrook/matanuska-adr-009-type-awareness-in-the-compiler-and-runtime-442f</link>
      <guid>https://dev.to/jfhbrook/matanuska-adr-009-type-awareness-in-the-compiler-and-runtime-442f</guid>
      <description>&lt;p&gt;&lt;em&gt;This article is a repost of an ADR from &lt;a href="https://github.com/jfhbrook/matanuska" rel="noopener noreferrer"&gt;Matanuska BASIC&lt;/a&gt;, my attempt to write a BASIC interpreter in TypeScript.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Context
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jfhbrook/matanuska-adr-007-type-semantics-for-primary-types-2m13"&gt;ADR 007&lt;/a&gt;, we specified the type semantics for operations on dissimilar types. The takeaway from this ADR is that there are many such operations which are invalid, and the ones which &lt;em&gt;are&lt;/em&gt; valid require implicit type casting. In other words, the types of values matter.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jfhbrook/matanuska-adr-008-sigils-3j9e"&gt;ADR 008&lt;/a&gt;, we decided to implement a standard BASIC model for manifest data types. To review, primary variables are typed using a postfix sigil, but those sigils don't distinguish them from arrays and functions. That means that we &lt;em&gt;sometimes&lt;/em&gt; have compile time type information.&lt;/p&gt;

&lt;p&gt;When available, the advantages of manifest data types are two-fold.&lt;/p&gt;

&lt;p&gt;First, the compiler can implement type checks - in other words, this introduces type safety. This can be accomplished by maintaining a stack of types in the compiler and comparing them at compile time. When the compiler detects an operation is being executed on incompatible types, it can throw an error prior to runtime execution. This is generally considered a better user experience.&lt;/p&gt;

&lt;p&gt;Second, the runtime can assume data types and use type-specific bytecode instructions. In a fully typed language like Java, the runtime can implement type-specific instructions. For example, suppose we are executing &lt;code&gt;1 + true&lt;/code&gt; and that our language casts boolean arguments to integers. In a dynamic typing regime, we would implement a generic &lt;code&gt;ADD&lt;/code&gt; instruction that checks the types of the two values and casts &lt;code&gt;true&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt; on the spot. But if we know a priori that &lt;code&gt;1&lt;/code&gt; is an integer and &lt;code&gt;true&lt;/code&gt; is a boolean, we could instead execute &lt;code&gt;CAST_INT_TO_BOOL, ADD_INTS&lt;/code&gt;, and these instructions can assume that their arguments are a bool and two ints respectively. In contrast, a dynamically typed language &lt;em&gt;must&lt;/em&gt; use a generic &lt;code&gt;ADD&lt;/code&gt; instruction.&lt;/p&gt;

&lt;p&gt;The trade-off here is that typed instructions require &lt;em&gt;more&lt;/em&gt; instructions, but each instruction requires less work. Unfortunately, a partially typed runtime would mean that we'd need to implement &lt;em&gt;both&lt;/em&gt; typed and dynamic instructions - in other words, we would need to implement &lt;code&gt;CAST_BOOL_TO_INT&lt;/code&gt; and &lt;code&gt;ADD_INT&lt;/code&gt; for when types are known, and generic &lt;code&gt;ADD&lt;/code&gt; for when types are unknown. This may still allow for optimized execution in cases where types are known, assuming that switch statements are cheap, but the runtime becomes undenably more complex.&lt;/p&gt;

&lt;p&gt;The alternative is to implement a dynamic runtime, and only check types in the compiler. This would mean instructions would still need to check types and do implicit casting, but it would also mean a simpler instruction set - and remember, this doesn't preclude the compiler implementing partial type safety. Note also that a fully dynamic runtime can be an incremental step. If we implement a fully dynamic runtime, we can always add type-specific instructions later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision
&lt;/h2&gt;

&lt;p&gt;First, we will implement type checks in the compiler by simulating a stack. These types will initially support primary types and an &lt;code&gt;Any&lt;/code&gt; type. This will allow the compiler to detect and throw type errors, giving an improved user experience.&lt;/p&gt;

&lt;p&gt;However, this work may be deferred until an unspecified date. It's more important that runtime behavior is correct than it is that type errors are caught in the compiler, and implementing it is relatively challenging - and low priority.&lt;/p&gt;

&lt;p&gt;Second, we will &lt;em&gt;not&lt;/em&gt; initially support typed operations in the runtime. This will likely manifest in a slower runtime as compared to one that &lt;em&gt;can&lt;/em&gt; assume types. But will also keep the scope of the initial implementation smaller, as well as leaving the door open for typed operations in the future.&lt;/p&gt;

</description>
      <category>basic</category>
      <category>typescript</category>
      <category>interpreters</category>
      <category>types</category>
    </item>
  </channel>
</rss>
