<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Parser on Electric Nexus</title><link>https://arkoniak.com/tags/parser/</link><description>Recent content in Parser on Electric Nexus</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 25 Aug 2025 09:14:24 +0300</lastBuildDate><atom:link href="https://arkoniak.com/tags/parser/index.xml" rel="self" type="application/rss+xml"/><item><title>Zig Peg Parser</title><link>https://arkoniak.com/posts/zig-peg-parser/</link><pubDate>Mon, 25 Aug 2025 09:14:24 +0300</pubDate><guid>https://arkoniak.com/posts/zig-peg-parser/</guid><description>Building a PEG parser in Zig, inspired by Guido van Rossum&amp;rsquo;s PEG series — first step towards a full B-Minor compiler.</description><content:encoded><![CDATA[<h2 id="intro">Intro</h2>
<p>This post is inspired by the amazing
<a href="https://medium.com/@gvanrossum_83706/peg-parsing-series-de5d41b2ed60">Guido van Rossum series on PEG building</a>
and follows my idea of building simple version of
<a href="https://dthain.github.io/books/compiler/chapterB.pdf">B-Minor language</a>.</p>
<p>I have already some experience in building simple recusrive descent parsers, so this is a good time to try to do something more complicated and PEG seems like a good choice. And as I am still in learning mode, I want to implement it in <code>Zig</code>, since I
like a lot manual allocations and many other features of this language (but of course there are many things that makes
me quite angry).</p>
<p>But enought talks, lets dive in.</p>
<h2 id="plan">Plan</h2>
<p>Here is the plan and constraints on how I am going to proceed.</p>
<ol>
<li>Implement basic arithmetic PEG parser, since rules for it are already provided in this nice
<a href="https://en.wikipedia.org/wiki/Parsing_expression_grammar">Wikipedia page</a></li>
<li>Add memoizing functionality.</li>
<li>Using this basic PEG parser as a foundation, implement compiler-compiler, which uses PEG rules and generates
the same parser.</li>
<li>Add more rules and as a result implement full <code>B-Minor</code> parser.</li>
</ol>
<h2 id="basic-arithmetic-parser">Basic Arithmetic parser</h2>
<p>We have following rules</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Expr    ← Sum
</span></span><span class="line"><span class="cl">Sum     ← Product ((&#39;+&#39; / &#39;-&#39;) Product)*
</span></span><span class="line"><span class="cl">Product ← Power ((&#39;*&#39; / &#39;/&#39;) Power)*
</span></span><span class="line"><span class="cl">Power   ← Value (&#39;^&#39; Power)?
</span></span><span class="line"><span class="cl">Value   ← NUMBER / &#39;(&#39; Expr &#39;)&#39;
</span></span></code></pre></div><p>Here <code>NUMBER</code> is a token, which corresponds to the <code>[0-9]+</code> expression. Of course, it is possible to make tokenization
part of the grammar, but I am not completely convinced that it is that useful, so I am ok with extra tokens here and there.</p>
<p>Also, while it is useful to have tokens like &lsquo;+&rsquo;/&rsquo;-&rsquo; etc in rules, it is more convinient to have them defined as proper
tags in tokenizer.</p>
<h3 id="tokenizer">Tokenizer</h3>
<p>Tokenizer is straightforward and very similar to what is written in <code>Python</code> tokenizer version. Main difference is that
since I do not have pre-ready tokenizer, I split tokenizer in two parts: <code>Scanner</code> which does all the heavy-lifintg, but
rather dumb and <code>Lexer</code> which is basically <code>Scanner</code> with buffer and allow me later to utilize infinite backtracking.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-zig" data-lang="zig"><span class="line"><span class="cl"><span class="kr">pub</span><span class="w"> </span><span class="kr">const</span><span class="w"> </span><span class="n">Lexer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scanner</span><span class="p">:</span><span class="w"> </span><span class="n">Scanner</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">tokens</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="nf">ArrayList</span><span class="p">(</span><span class="n">Token</span><span class="p">),</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">pos</span><span class="p">:</span><span class="w"> </span><span class="kt">u32</span><span class="p">,</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">const</span><span class="w"> </span><span class="n">Self</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">@This</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">:</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="n">mem</span><span class="p">.</span><span class="n">Allocator</span><span class="p">,</span><span class="w"> </span><span class="n">scanner</span><span class="p">:</span><span class="w"> </span><span class="n">Scanner</span><span class="p">)</span><span class="w"> </span><span class="n">Self</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">tokens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span><span class="p">.</span><span class="nf">ArrayList</span><span class="p">(</span><span class="n">Token</span><span class="p">).</span><span class="nf">init</span><span class="p">(</span><span class="n">allocator</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="p">.{</span><span class="w"> </span><span class="p">.</span><span class="n">scanner</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">scanner</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">tokens</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">tokens</span><span class="p">,</span><span class="w"> </span><span class="p">.</span><span class="n">pos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">};</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">mark</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="kt">u32</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">pos</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">reset</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="o">*</span><span class="n">Self</span><span class="p">,</span><span class="w"> </span><span class="n">pos</span><span class="p">:</span><span class="w"> </span><span class="kt">u32</span><span class="p">)</span><span class="w"> </span><span class="kt">void</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">self</span><span class="p">.</span><span class="n">pos</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">pos</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">nextToken</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="o">*</span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="n">Token</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kr">const</span><span class="w"> </span><span class="n">token</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">try</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="nf">peekToken</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">self</span><span class="p">.</span><span class="n">pos</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">token</span><span class="p">;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kr">pub</span><span class="w"> </span><span class="k">fn</span><span class="w"> </span><span class="nf">peekToken</span><span class="p">(</span><span class="n">self</span><span class="p">:</span><span class="w"> </span><span class="o">*</span><span class="n">Self</span><span class="p">)</span><span class="w"> </span><span class="o">!</span><span class="n">Token</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">pos</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">tokens</span><span class="p">.</span><span class="n">items</span><span class="p">.</span><span class="n">len</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">tokens</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="n">scanner</span><span class="p">.</span><span class="nf">nextToken</span><span class="p">());</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"> </span><span class="n">self</span><span class="p">.</span><span class="n">tokens</span><span class="p">.</span><span class="n">items</span><span class="p">[</span><span class="n">self</span><span class="p">.</span><span class="n">pos</span><span class="p">];</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">};</span><span class="w">
</span></span></span></code></pre></div><p>Of course, at some point I should add errors resolution. It is nice to ride on auto inference, but as we know, <code>Zig</code> is
not good at resolving error unions from recursive calls and we have plenty of those. Alternative is to <code>panic</code> all the
time, since most of the important errors arises from memory manipulation in <code>ArrayList</code> and there is not much I can do
about it anyway.</p>
<p>Another thing is that you do not find any <code>deinit</code> in this code, since it is assumed, that <code>Arena Allocator</code> is used.
Of course, it is possible to sprinkle everything with <code>destroy</code>, <code>free</code> and <code>deinit</code> just in case, but I do not see it
as a reasonable thing. It only adds extra burden of tracking memory leaks, but parsing is a one-time procedure, so all
memory should be released anyway. It is faster, it is easier, it is more reliable and it is one the things I love so
nuch about <code>Zig</code>.</p>
<h2 id="memoizing">Memoizing</h2>
<p>Needs to figure out, how to do it in <code>Zig</code>.</p>
]]></content:encoded></item></channel></rss>