<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://sgeos.github.io/feed/rust.xml" rel="self" type="application/atom+xml" /><link href="https://sgeos.github.io/" rel="alternate" type="text/html" /><updated>2026-07-12T01:04:06+00:00</updated><id>https://sgeos.github.io/feed/rust.xml</id><title type="html">Brendan A R Sechter’s Development Blog | Rust</title><subtitle>A personal technical notebook covering systems programming, systems philosophy, tooling, mathematics, and emerging software paradigms.
</subtitle><author><name>Brendan Sechter</name></author><entry><title type="html">Getting Started with Keleusma 0.2.2</title><link href="https://sgeos.github.io/rust/embedded/programming/2026/07/10/keleusma_0_2_2_getting_started.html" rel="alternate" type="text/html" title="Getting Started with Keleusma 0.2.2" /><published>2026-07-10T12:00:00+00:00</published><updated>2026-07-10T12:00:00+00:00</updated><id>https://sgeos.github.io/rust/embedded/programming/2026/07/10/keleusma_0_2_2_getting_started</id><content type="html" xml:base="https://sgeos.github.io/rust/embedded/programming/2026/07/10/keleusma_0_2_2_getting_started.html"><![CDATA[<!-- A205 -->
<script>console.log("A205");</script>

<p><a href="https://github.com/sgeos/keleusma">Keleusma</a> is a total functional stream processor
that compiles to bytecode
and runs on a stack-based virtual machine.
The language ships with a static worst-case-execution-time bound
and a static worst-case-memory-usage bound
that a load-time verifier proves
before any program runs.
The 0.2.0 release covered
in <a href="/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html">an earlier article</a>
introduced cryptographic module signing,
information-flow labels,
newtypes with refinement predicates,
and a reset instruction-set architecture.
The 0.1.1 pre-release was covered
in <a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">the first article of this series</a>.</p>

<p>Version 0.2.1 was tagged on 2026-07-08.
It is a consolidation release
that fills gaps in the surface syntax,
adds a general const-generics facility,
turns scripts into first-class shell citizens,
provides source-level debugging support,
tightens the load-time verifier,
and adds an operator-configured deployment policy
for signed and encrypted bytecode.
Version 0.2.2 was tagged on 2026-07-09,
the day after 0.2.1.
It is a build-fix and tooling release
on the self-hosting groundwork line.
It repairs cross-target and continuous-integration regressions
from 0.2.1
that broke the flagship Cortex-M embedded targets
and the <code class="language-plaintext highlighter-rouge">verify</code>-without-<code class="language-plaintext highlighter-rouge">floats</code> feature combination,
lands the learning guide
as a bilingual mdbook
that is now served
at
<a href="https://sgeos.github.io/keleusma/">the hosted book URL</a>,
lands the initial scaffold
of
the self-hosted-compiler subproject
that the 0.3.0 release will complete,
and codifies the release process
with a mandatory green-continuous-integration gate.
The language surface
is
unchanged from 0.2.1,
and no wire-format or bytecode-version change accompanies the release.
The self-hosting concept was treated
for the software case
in <a href="/compilers/streaming/series/2026/04/17/stream_processor_as_compiler_and_compiler_as_stream_processor.html">the streaming compilers series conclusion</a>
and for silicon in
<a href="/hdl/hardware/self-hosting/2026/07/09/self_hosted_silicon_compiler.html">the recent hardware article</a>.</p>

<p>Readers who want to try the language
without installing anything
can use
<a href="https://sgeos.github.io/keleusma/playground/">the browser-based playground</a>,
which compiles and verifies programs
through
a WebAssembly build of the compiler
and reports
worst-case execution time
and memory bounds
live.
The playground is served
alongside the hosted book.</p>

<p>This article walks through the material additions of the 0.2.x line
with runnable examples,
covering
the 0.2.1 language features
that 0.2.2 preserves
and
the 0.2.2 tooling additions
that are relevant to a getting-started walkthrough.
Every code listing below was executed with the version
recorded in the Software Versions section,
and every reported output is the actual output produced.
The article is an on-ramp for readers already familiar with 0.2.0.
Readers new to the language should start
with <a href="https://sgeos.github.io/keleusma/">the bundled guide</a>
and its <a href="https://sgeos.github.io/keleusma/02_installing_and_running.html">installation chapter</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-07-10 22:05:01 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026<span class="p">;</span> root:xnu-12377.121.6~2/RELEASE_ARM64_T6000 arm64

<span class="c"># Keleusma</span>
<span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.2
</code></pre></div></div>

<h2 id="installation">Installation</h2>

<p>Keleusma 0.2.2 is published on <a href="https://crates.io/crates/keleusma">crates.io</a> as a library
and as the separate command-line crate <a href="https://crates.io/crates/keleusma-cli"><code class="language-plaintext highlighter-rouge">keleusma-cli</code></a>.
The source lives on <a href="https://github.com/sgeos/keleusma">GitHub</a>
and the application-programming-interface documentation is on <a href="https://docs.rs/keleusma/0.2.2">docs.rs</a>.
The install path is unchanged from 0.2.0.
The 0.2.2 release additionally repairs
the 32-bit and <code class="language-plaintext highlighter-rouge">no_std</code> embedded builds
that 0.2.1 broke,
so a fresh install
from source
on the flagship Cortex-M targets
now succeeds without a manual patch.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/sgeos/keleusma
<span class="nb">cd </span>keleusma
cargo <span class="nb">install</span> <span class="nt">--path</span> keleusma-cli <span class="nt">--bin</span> keleusma
</code></pre></div></div>

<p>Confirm the installation.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.2
</code></pre></div></div>

<p>To embed the runtime in a Rust program rather than use the tool,
add the library crates to a project.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">keleusma</span> <span class="p">=</span> <span class="s">"0.2"</span>
<span class="py">keleusma-arena</span> <span class="p">=</span> <span class="s">"0.3.1"</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">keleusma-arena</code> version requirement
tightens from <code class="language-plaintext highlighter-rouge">0.3</code> to <code class="language-plaintext highlighter-rouge">0.3.1</code>
in 0.2.2
because the runtime now consumes
additive helpers
that
<code class="language-plaintext highlighter-rouge">keleusma-arena</code> 0.3.1
exposes.</p>

<h2 id="boolean-bitwise-and-shift-operators">Boolean, Bitwise, and Shift Operators</h2>

<p>Version 0.2.0 added the five bitwise opcodes
<code class="language-plaintext highlighter-rouge">BitAnd</code>, <code class="language-plaintext highlighter-rouge">BitOr</code>, <code class="language-plaintext highlighter-rouge">BitXor</code>, <code class="language-plaintext highlighter-rouge">Shl</code>, and <code class="language-plaintext highlighter-rouge">Shr</code>
without a grammar to reach them from source.
Version 0.2.1 supplies the grammar
and, in the same pass,
rearranges the boolean operators
so that the two families never disambiguate by operand type.</p>

<p>The bitwise family uses the letter-prefixed names
<code class="language-plaintext highlighter-rouge">band</code>, <code class="language-plaintext highlighter-rouge">bor</code>, <code class="language-plaintext highlighter-rouge">bxor</code>, and the prefix <code class="language-plaintext highlighter-rouge">bnot</code>.
The operators apply to <code class="language-plaintext highlighter-rouge">Word</code>, <code class="language-plaintext highlighter-rouge">Byte</code>, and the parameterized <code class="language-plaintext highlighter-rouge">Multiword&lt;N&gt;</code>.
On a <code class="language-plaintext highlighter-rouge">Multiword</code> the operation runs limb by limb
with no cross-limb interaction.
On a <code class="language-plaintext highlighter-rouge">Byte</code> the operation runs at the byte width,
so <code class="language-plaintext highlighter-rouge">bnot 0Byte</code> is <code class="language-plaintext highlighter-rouge">255Byte</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">mask</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mb">0b1100</span><span class="w"> </span><span class="n">band</span><span class="w"> </span><span class="mb">0b1010</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">all</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mb">0b1100</span><span class="w"> </span><span class="n">bor</span><span class="w"> </span><span class="mb">0b0011</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">flipped</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mb">0b1010</span><span class="w"> </span><span class="n">bxor</span><span class="w"> </span><span class="mb">0b1111</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">inverted</span><span class="p">:</span><span class="w"> </span><span class="kt">Byte</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">bnot</span><span class="w"> </span><span class="err">0</span><span class="kt">Byte</span><span class="p">;</span><span class="w">
    </span><span class="n">mask</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">all</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">flipped</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">inverted</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 01_bitwise.kel
283
</code></pre></div></div>

<p>The shift family uses the assembly mnemonics
<code class="language-plaintext highlighter-rouge">lsl</code> (logical left),
<code class="language-plaintext highlighter-rouge">asl</code> (arithmetic left),
<code class="language-plaintext highlighter-rouge">lsr</code> (logical right),
and <code class="language-plaintext highlighter-rouge">asr</code> (arithmetic right).
The <code class="language-plaintext highlighter-rouge">asl</code> and <code class="language-plaintext highlighter-rouge">lsl</code> operators produce the same bit pattern
but <code class="language-plaintext highlighter-rouge">asl</code> denotes the multiplicative interpretation <code class="language-plaintext highlighter-rouge">x * 2^k</code>
and therefore admits the <code class="language-plaintext highlighter-rouge">overflow</code> and <code class="language-plaintext highlighter-rouge">underflow</code> arms
of the checked-arithmetic construct.
A variable shift amount is admissible
and the worst-case bound is preserved,
because the multi-word case is unrolled
over the compile-time word count
with runtime index arithmetic
and branch-free bounds guards.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">left</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">lsl</span><span class="w"> </span><span class="mi">4</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">arith_left</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">asl</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">logical_right</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">128</span><span class="w"> </span><span class="n">lsr</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">arith_right</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">0</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">8</span><span class="p">)</span><span class="w"> </span><span class="n">asr</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
    </span><span class="n">left</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">arith_left</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">logical_right</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">arith_right</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 02_shift.kel
40
</code></pre></div></div>

<p>The boolean family has two subfamilies.
The eager <code class="language-plaintext highlighter-rouge">and</code>, <code class="language-plaintext highlighter-rouge">or</code>, <code class="language-plaintext highlighter-rouge">xor</code>, and prefix <code class="language-plaintext highlighter-rouge">not</code>
always evaluate both operands.
The short-circuit <code class="language-plaintext highlighter-rouge">andalso</code> and <code class="language-plaintext highlighter-rouge">orelse</code>
skip the right operand
when the left already decides the result.
The eager forms are the branch-free default
because a definitive worst-case-execution-time bound
prefers branch-free code.
The short-circuit forms remain available
for cases where skipping the right operand is intended.
Selection is by operator name
and is never inferred from operand type.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="ow">or</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="n">xor</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">d</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">not</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">e</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="w"> </span><span class="n">andalso</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">f</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="w"> </span><span class="n">orelse</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">count</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w">
        </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w">
        </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w">
        </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w">
        </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">d</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w">
        </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">e</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">})</span><span class="w">
        </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">f</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">});</span><span class="w">
    </span><span class="n">count</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 03_boolean.kel
42
</code></pre></div></div>

<h2 id="general-const-generics">General Const Generics</h2>

<p>Version 0.2.0 accepted a fixed-point width parameter
on the <code class="language-plaintext highlighter-rouge">Multiword&lt;N, F&gt;</code> type as a special case.
Version 0.2.1 replaces the special case
with a general const-generics facility.
A definition may be parameterized
by a compile-time constant of type <code class="language-plaintext highlighter-rouge">Word</code>
in addition to its type parameters.
The parameter is introduced by the <code class="language-plaintext highlighter-rouge">const</code> keyword
and serves in a type position
as an array length or a <code class="language-plaintext highlighter-rouge">Multiword</code> parameter,
and in a value position inside a function body
as an ordinary <code class="language-plaintext highlighter-rouge">Word</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">tag_first</span><span class="o">&lt;</span><span class="kr">const</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="o">&gt;</span><span class="p">(</span><span class="n">buf</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="kt">Word</span><span class="p">;</span><span class="w"> </span><span class="n">n</span><span class="p">])</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">buf</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">buf</span><span class="p">[</span><span class="n">n</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 class="o">+</span><span class="w"> </span><span class="n">n</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">five</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="kt">Word</span><span class="p">;</span><span class="w"> </span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">,</span><span class="w"> </span><span class="mi">50</span><span class="p">];</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">three</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="kt">Word</span><span class="p">;</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">7</span><span class="p">,</span><span class="w"> </span><span class="mi">14</span><span class="p">,</span><span class="w"> </span><span class="mi">21</span><span class="p">];</span><span class="w">
    </span><span class="n">tag_first</span><span class="o">::&lt;</span><span class="mi">5</span><span class="o">&gt;</span><span class="p">(</span><span class="n">five</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">tag_first</span><span class="o">::&lt;</span><span class="mi">3</span><span class="o">&gt;</span><span class="p">(</span><span class="n">three</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 04_const_generics.kel
96
</code></pre></div></div>

<p>Const arguments are always explicit
because they cannot be inferred from value arguments.
A call writes a turbofish <code class="language-plaintext highlighter-rouge">f::&lt;8&gt;(...)</code>,
a struct construction writes <code class="language-plaintext highlighter-rouge">Buf::&lt;8&gt; { ... }</code>,
and a type reference writes <code class="language-plaintext highlighter-rouge">Buf&lt;8&gt;</code>.
A const argument may be a total arithmetic expression
over <code class="language-plaintext highlighter-rouge">+</code>, <code class="language-plaintext highlighter-rouge">-</code>, and <code class="language-plaintext highlighter-rouge">*</code>,
so <code class="language-plaintext highlighter-rouge">Buf&lt;n + 1&gt;</code> and <code class="language-plaintext highlighter-rouge">Multiword&lt;2 * n&gt;</code> are admissible.
Division and modulo are excluded from const arithmetic
so evaluation is total.</p>

<p>Monomorphization substitutes every const parameter to a concrete literal
before the load-time analyses run.
Every array length,
every <code class="language-plaintext highlighter-rouge">Multiword</code> parameter,
and every loop bound in the specialized code
is therefore a literal,
and the worst-case-execution-time
and worst-case-memory-usage analyses
observe no symbolic constant.
The static bounds are preserved unchanged.</p>

<h2 id="executable-scripts">Executable Scripts</h2>

<p>A Keleusma script may begin with a shebang line
and become a directly executable file on Unix.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env keleusma</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="mi">12</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">42</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Mark it executable and run it as a command.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">chmod</span> +x 05_shebang.kel
<span class="nv">$ </span>./05_shebang.kel
504
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">keleusma</code> command-line frontend runs a bare path as a <code class="language-plaintext highlighter-rouge">run</code> invocation,
and the lexer skips the shebang line
while preserving source line numbers in diagnostics.
Combined with the <code class="language-plaintext highlighter-rouge">shell</code> native bundle,
a script becomes a portable command-line orchestrator
that delegates work to POSIX tools,
drives control flow on the returned <code class="language-plaintext highlighter-rouge">Word</code> exit codes,
and sets its own process exit code with <code class="language-plaintext highlighter-rouge">shell::exit</code>.</p>

<h2 id="script-arguments">Script Arguments</h2>

<p>The <code class="language-plaintext highlighter-rouge">shell</code> bundle exposes a script’s own arguments
through <code class="language-plaintext highlighter-rouge">shell::arg(i)</code> and <code class="language-plaintext highlighter-rouge">shell::arg_count()</code>.
Index zero is the script path,
and indices one and above are the positional arguments
that the launcher passed after it,
mirroring the shell variables <code class="language-plaintext highlighter-rouge">$0</code> and <code class="language-plaintext highlighter-rouge">$1</code>.
The <code class="language-plaintext highlighter-rouge">shell::arg</code> native returns <code class="language-plaintext highlighter-rouge">Option&lt;Text&gt;</code>
so an out-of-range index is a total operation
that the script destructures with <code class="language-plaintext highlighter-rouge">match</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">#!/usr/bin/env keleusma</span><span class="w">

</span><span class="k">use</span><span class="w"> </span><span class="n">shell</span><span class="o">::</span><span class="n">arg</span><span class="w">
</span><span class="k">use</span><span class="w"> </span><span class="n">shell</span><span class="o">::</span><span class="n">arg_count</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">label</span><span class="p">(</span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">Option</span><span class="o">&lt;</span><span class="kt">Text</span><span class="o">&gt;</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">match</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="kt">Option</span><span class="o">::</span><span class="nc">Some</span><span class="p">(</span><span class="n">_name</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w">
        </span><span class="kt">Option</span><span class="o">::</span><span class="nc">None</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">shell</span><span class="o">::</span><span class="n">arg_count</span><span class="p">();</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">first_present</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">label</span><span class="p">(</span><span class="n">shell</span><span class="o">::</span><span class="n">arg</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span><span class="w">
    </span><span class="n">n</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">first_present</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">chmod</span> +x 06_args.kel
<span class="nv">$ </span>./06_args.kel alpha beta
31
</code></pre></div></div>

<p>The output encodes the argument count <code class="language-plaintext highlighter-rouge">n = 3</code>
in the tens digit
and the presence of index one
in the ones digit.
The command-line frontend also accepts a <code class="language-plaintext highlighter-rouge">--</code> terminator
after which every token is treated as a script argument
regardless of shape.
A companion native <code class="language-plaintext highlighter-rouge">shell::run_full(cmd) -&gt; (Word, Text, Text)</code>
returns the exit code together with both standard-output
and standard-error streams,
complementing <code class="language-plaintext highlighter-rouge">shell::run</code> which captures and discards
the error stream.</p>

<h2 id="debug-assertions">Debug Assertions</h2>

<p>The new <code class="language-plaintext highlighter-rouge">assert</code> statement expresses a compile-out debug assertion
over a <code class="language-plaintext highlighter-rouge">bool</code> condition.
An optional message argument attaches a diagnostic string.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">safe_scale</span><span class="p">(</span><span class="n">base</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w"> </span><span class="n">factor</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">assert</span><span class="w"> </span><span class="n">factor</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s2">"factor must be non-negative"</span><span class="p">;</span><span class="w">
    </span><span class="n">base</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">factor</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">safe_scale</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">7</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Under a debug build the compiler emits a runtime check
that traps with <code class="language-plaintext highlighter-rouge">VmError::AssertionFailed</code>
when the condition is false,
together with a strippable debug record
carrying the source span
and the optional message.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma compile 07_assert.kel <span class="nt">--debug</span> <span class="nt">-o</span> 07_assert.bin
wrote 07_assert.bin <span class="o">(</span>3812 bytes<span class="o">)</span>
<span class="nv">$ </span>keleusma run 07_assert.bin
42
</code></pre></div></div>

<p>The next example flips the argument sign
and shows the diagnostic.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">safe_scale</span><span class="p">(</span><span class="n">base</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w"> </span><span class="n">factor</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">assert</span><span class="w"> </span><span class="n">factor</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="s2">"factor must be non-negative"</span><span class="p">;</span><span class="w">
    </span><span class="n">base</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">factor</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">safe_scale</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">7</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma compile 07b_assert_fail.kel <span class="nt">--debug</span> <span class="nt">-o</span> 07b_assert_fail.bin
wrote 07b_assert_fail.bin <span class="o">(</span>3892 bytes<span class="o">)</span>
<span class="nv">$ </span>keleusma run 07b_assert_fail.bin
error: vm: AssertionFailed
</code></pre></div></div>

<p>Under an ordinary compile the statement compiles out entirely
and contributes no opcodes.
The <code class="language-plaintext highlighter-rouge">assert</code> keyword is not reserved
outside statement position,
so <code class="language-plaintext highlighter-rouge">assert(x)</code> at expression position
remains a call to a user-defined function.
The virtual machine also records the source position of the trap
through the internal <code class="language-plaintext highlighter-rouge">fault_location</code> field,
which a host program consumes
through the <code class="language-plaintext highlighter-rouge">Vm::fault_source_location()</code> application-programming-interface
to map the trap to a source span.
The command-line frontend used for the demonstration above
prints the <code class="language-plaintext highlighter-rouge">VmError</code> alone
and leaves the span-resolution step
to a host program that consumes the library directly.</p>

<h2 id="partial-operation-handling">Partial Operation Handling</h2>

<p>Every mathematically partial operation now has a defined contract
and an opt-in source-level handling construct,
so a program can be made total at the source level
rather than relying on a runtime trap.
Checked arithmetic over <code class="language-plaintext highlighter-rouge">Word</code>, <code class="language-plaintext highlighter-rouge">Byte</code>, <code class="language-plaintext highlighter-rouge">Float</code>, and <code class="language-plaintext highlighter-rouge">Fixed&lt;N&gt;</code>
uses the arm family <code class="language-plaintext highlighter-rouge">ok</code>, <code class="language-plaintext highlighter-rouge">overflow</code>, <code class="language-plaintext highlighter-rouge">underflow</code>, and <code class="language-plaintext highlighter-rouge">zero_divisor</code>.
An omitted <code class="language-plaintext highlighter-rouge">overflow</code> or <code class="language-plaintext highlighter-rouge">underflow</code> arm
defaults to two’s-complement wrapping.
Inside an arm body
the keywords <code class="language-plaintext highlighter-rouge">saturate_max</code> and <code class="language-plaintext highlighter-rouge">saturate_min</code>
stand for the largest and smallest value of the operand type
and let a program clamp an out-of-range result
to the edge of the range.
Array indexing uses <code class="language-plaintext highlighter-rouge">invalid_index</code>.
Refinement-newtype construction uses <code class="language-plaintext highlighter-rouge">invalid_newtype</code>.
The discriminant-to-enum conversion uses <code class="language-plaintext highlighter-rouge">ok</code>,
<code class="language-plaintext highlighter-rouge">payload_discriminant</code>,
and <code class="language-plaintext highlighter-rouge">invalid_discriminant</code>.
Fallible native calls use <code class="language-plaintext highlighter-rouge">error(code)</code>,
with the host reporting the <code class="language-plaintext highlighter-rouge">Word</code> code
through the new <code class="language-plaintext highlighter-rouge">KeleusmaError</code> derive.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">safe_div</span><span class="p">(</span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">a</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nb">ok</span><span class="p">(</span><span class="n">q</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">q</span><span class="p">,</span><span class="w">
        </span><span class="n">zero_divisor</span><span class="p">(</span><span class="n">_n</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">saturate_add_byte</span><span class="p">(</span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">Byte</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">Byte</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Byte</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nb">ok</span><span class="p">(</span><span class="n">v</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">v</span><span class="p">,</span><span class="w">
        </span><span class="nb">overflow</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="nb">saturate_max</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">q</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">safe_div</span><span class="p">(</span><span class="mi">84</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">z</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">safe_div</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">s</span><span class="p">:</span><span class="w"> </span><span class="kt">Byte</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">saturate_add_byte</span><span class="p">(</span><span class="err">200</span><span class="kt">Byte</span><span class="p">,</span><span class="w"> </span><span class="err">100</span><span class="kt">Byte</span><span class="p">);</span><span class="w">
    </span><span class="n">q</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="p">(</span><span class="n">s</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 09_partial.kel
297
</code></pre></div></div>

<p>The three checked operations produce
<code class="language-plaintext highlighter-rouge">safe_div(84, 2) = 42</code>,
<code class="language-plaintext highlighter-rouge">safe_div(10, 0) = 0</code> through the <code class="language-plaintext highlighter-rouge">zero_divisor</code> arm,
and <code class="language-plaintext highlighter-rouge">saturate_add_byte(200Byte, 100Byte) = 255Byte</code>
through the saturating <code class="language-plaintext highlighter-rouge">overflow</code> arm,
summing to <code class="language-plaintext highlighter-rouge">297</code>.
The virtual machine traps recoverably
on an unhandled partial operation
through specific <code class="language-plaintext highlighter-rouge">VmError</code> variants.
The specification of every runtime fault
lives in the reference document
<a href="https://sgeos.github.io/keleusma/23_big_numbers.html">Handling Partial Operations</a>.</p>

<h2 id="strippable-debug-metadata">Strippable Debug Metadata</h2>

<p>Compiled bytecode can carry optional per-chunk debug metadata
that maps op-stream positions back to source.
The compile flag <code class="language-plaintext highlighter-rouge">--debug</code> emits it,
and the new subcommand <code class="language-plaintext highlighter-rouge">keleusma strip</code> removes it,
producing a release artefact
byte-identical to a non-debug compile of the same source.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma compile 08_strip.kel <span class="nt">-o</span> 08_release.bin
wrote 08_release.bin <span class="o">(</span>2456 bytes<span class="o">)</span>

<span class="nv">$ </span>keleusma compile 08_strip.kel <span class="nt">--debug</span> <span class="nt">-o</span> 08_debug.bin
wrote 08_debug.bin <span class="o">(</span>3372 bytes<span class="o">)</span>

<span class="nv">$ </span>keleusma strip 08_debug.bin <span class="nt">-o</span> 08_stripped.bin
stripped 08_debug.bin -&gt; 08_stripped.bin <span class="o">(</span>2456 bytes<span class="o">)</span>

<span class="nv">$ </span>cmp 08_release.bin 08_stripped.bin <span class="o">&amp;&amp;</span> <span class="nb">echo</span> <span class="s2">"IDENTICAL"</span>
IDENTICAL
</code></pre></div></div>

<p>The metadata lives in a chunk-local pool
in the wire format’s auxiliary body
and never in the opcode stream.
A debug build and a release build
therefore share an identical opcode stream
for the same source,
and stripping is a pure subtraction rather than a transform.
The subcommand refuses signed or encrypted input,
because rewriting the body invalidates a signature.
The supported ordering is compile,
then strip,
then sign.</p>

<p>The metadata catalogue covers twelve record kinds
including source-span records for statements,
line-number records,
variable-name records,
call-site records,
type annotations,
information-flow-label annotations,
generic-instantiation records,
verifier witnesses,
worst-case-execution-time markers,
breakpoint candidates,
assertion contexts,
and optimisation markers at refinement-elision sites.</p>

<h2 id="deployment-policy-for-signed-and-encrypted-bytecode">Deployment Policy for Signed and Encrypted Bytecode</h2>

<p>Building on the 0.2.0 module-signing facility,
the command-line frontend gains an operator-configured execution policy
that constrains which bytecode may run,
analogous to an enrolled-key model
in a firmware trust framework.</p>

<p>Strict signing activates when a trust store is in force.
Configuration is by the environment variable
<code class="language-plaintext highlighter-rouge">KELEUSMA_TRUSTED_KEYS_DIR</code> naming a directory of <code class="language-plaintext highlighter-rouge">*.pub</code> verifying keys,
or by the platform-conventional directory
<code class="language-plaintext highlighter-rouge">/etc/keleusma/trusted_keys</code>,
or by the force flag <code class="language-plaintext highlighter-rouge">KELEUSMA_REQUIRE_SIGNED=1</code>.
When strict signing is active,
the frontend rejects source files and unsigned bytecode,
admits signed bytecode only when the signature validates
against an enrolled signer,
and rejects the command-line flag <code class="language-plaintext highlighter-rouge">--verifying-key</code>
so an unprivileged operator cannot relax the system-managed trust list.
Strict encryption activates symmetrically
through <code class="language-plaintext highlighter-rouge">KELEUSMA_DECRYPTION_KEYS_DIR</code>
and <code class="language-plaintext highlighter-rouge">KELEUSMA_REQUIRE_ENCRYPTED</code>.</p>

<p>The two modes compose into four policy states
from the permissive default
through fully locked-down.
The operator manual with air-gapped,
production-fleet,
and kiosk deployment scenarios
is the reference document
<a href="https://sgeos.github.io/keleusma/SECURITY_POLICY.html">Security Policy</a>.</p>

<h2 id="under-the-hood">Under the Hood</h2>

<p>Three internal changes in 0.2.1 are worth naming
even though the source language is not directly affected.</p>

<p>The composite runtime representation is now flat bytes
resident in the host arena
rather than heap-allocated <code class="language-plaintext highlighter-rouge">Vec</code> and <code class="language-plaintext highlighter-rouge">String</code> graphs.
The runtime <code class="language-plaintext highlighter-rouge">Value</code> slot is thirty-two bytes,
down from forty,
pinned by a compile-time size assertion.
Composite construction is a bump-pointer allocation
in the arena’s transient region
with no global allocator,
so a composite-building script runs on a <code class="language-plaintext highlighter-rouge">no_std</code> target
without a global heap.
Worst-case-memory-usage bounds are correspondingly tighter
and now reflect the language’s fixed-size guarantee
rather than the previous <code class="language-plaintext highlighter-rouge">Vec</code> and <code class="language-plaintext highlighter-rouge">String</code> over-approximation.</p>

<p>The load-time verifier gains a typed operand-stack pass
after the manner of the Java Virtual Machine
and WebAssembly verifiers.
The pass reconstructs the flat shape
of every operand-stack entry and local slot
by a bytecode-level type-preservation abstract interpretation.
It validates every compiler-baked composite,
field,
and array-element offset
against the canonical flat layout of the accessed type,
closing several audit findings
that were previously trusted at runtime under a debug assertion.
It upgrades the operand-depth pass
from max-of-branch-depths
to an exact-height join.
It enforces loop back-edge operand-stack neutrality.
And it validates wire-carried shared-slot offsets
against the shared-data buffer.</p>

<p>Trait methods on generic structs and enums now resolve
on a concrete, type-generic, or const-generic receiver alike.
Monomorphization specializes each generic implementation once
per concrete instantiation of its target type,
substituting the implementation’s type and const parameters
through the method signatures
and bodies,
so a call on a <code class="language-plaintext highlighter-rouge">Cell&lt;Word&gt;</code> receiver reaches the specialized method
even when the source implementation was written against a generic <code class="language-plaintext highlighter-rouge">Cell&lt;T&gt;</code>.</p>

<h2 id="toward-a-self-hosted-compiler">Toward a Self-Hosted Compiler</h2>

<p>Version 0.2.2 lands
the initial scaffold
of
the self-hosted-compiler subproject
that the 0.3.0 release will complete.
The scaffold
lives at
<code class="language-plaintext highlighter-rouge">compiler/</code>
in the repository
and comprises
the three-stage <code class="language-plaintext highlighter-rouge">loop</code> pipeline skeleton,
namely <code class="language-plaintext highlighter-rouge">lexer</code>, <code class="language-plaintext highlighter-rouge">parser</code>, and <code class="language-plaintext highlighter-rouge">codegen</code>,
along with
a Rust host driver
and
a release-by-release implementation plan.
No stage is implemented in 0.2.2.
The V0.2.x line
lands its prerequisites
across
the operator surface,
the const-generics facility,
the flat-byte composite representation,
the typed operand-stack verifier pass,
the debug metadata,
and
the strict-mode deployment policy
that
the earlier sections of this article
walk through.
Version 0.3.0
will
turn the scaffold
into a working compiler
written in Keleusma
that compiles Keleusma.</p>

<p>The self-hosting concept was treated at length
for the software case
in <a href="/compilers/streaming/series/2026/04/17/stream_processor_as_compiler_and_compiler_as_stream_processor.html">the streaming compilers series conclusion</a>,
which discusses the coalgebraic fixed-point condition
that a self-hosted compiler satisfies.
It was treated for the silicon case
in <a href="/hdl/hardware/self-hosting/2026/07/09/self_hosted_silicon_compiler.html">the recent article on self-hosted silicon compilation</a>.
The Keleusma standardization effort
sits on the software side of that boundary
and offers a candidate example
of a compact-toolchain language design
that a self-hosting compiler
could reasonably compile itself with.</p>

<h2 id="going-deeper">Going Deeper</h2>

<p>This article covers the material additions of the 0.2.x line
that are relevant to a getting-started walkthrough.
The complete language reference
is <a href="https://sgeos.github.io/keleusma/">the hosted book</a>,
whose 0.2.2 release migrated
the previously loose Markdown guide
into an mdbook
served
at
<code class="language-plaintext highlighter-rouge">https://sgeos.github.io/keleusma/</code>.
The book is bilingual
with English as the source
and Japanese
as
a gettext-based translation
that
0.2.2 also ships.
It teaches Keleusma from first principles
in a forty-chapter track
and covers the embedding surface for Rust hosts
in a second track.
Readers who prefer
to try the language
without installing anything
can use
<a href="https://sgeos.github.io/keleusma/playground/">the browser-based playground</a>,
which
runs the compiler as WebAssembly
in the reader’s browser
and is served
from the hosted book site.
The reference for handling partial operations
is <a href="https://sgeos.github.io/keleusma/23_big_numbers.html">the partial-operations chapter</a>.
The reference for information-flow labels
is <a href="https://sgeos.github.io/keleusma/24_information_flow_labels.html">the labels chapter</a>.
The reference for deployment-policy configuration
is the <a href="https://sgeos.github.io/keleusma/SECURITY_POLICY.html">Security Policy</a> document.
The reference for shebang-executable scripts
is the <a href="https://sgeos.github.io/keleusma/AUTOMATION_SCRIPTING.html">Automation and Scripting</a> document.
The bundled <a href="https://github.com/sgeos/keleusma/tree/v0.2.2/examples/scripts">example scripts</a>
are the seed material the guide builds on.</p>

<p>Two companion articles apply Keleusma to specific problem shapes.
<a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">A verifiable control kernel</a>
develops the language around a small runtime kernel.
<a href="/security/rust/programming/2026/05/29/information_flow_control_deep_dive_with_keleusma.html">Information-flow control, a deep dive</a>
develops the language around the security-labelled data model
that Version 0.2.0 introduced.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Neither 0.2.1 nor 0.2.2 changes
the central promise the language makes.
Every accepted program still carries a static proof
of bounded execution time
and bounded memory usage.
What 0.2.1 adds
is completeness on the surface syntax
where 0.2.0 left gaps,
a general const-generics facility
that supersedes the earlier special case,
first-class scripting ergonomics
that turn a Keleusma file into a command-line tool,
source-level debugging support
that a debugger or host program can consume,
a tightened load-time verifier
that closes several audit findings,
and an operator-configured deployment policy
for signed and encrypted bytecode.
What 0.2.2 adds
is
the initial scaffold
of the self-hosted-compiler subproject,
the learning guide
as
a bilingual mdbook
that is now
hosted online,
a browser-based playground
that runs the compiler
as WebAssembly,
and a codified release process
with
a mandatory green-continuous-integration gate.
The 0.2.2 release also repairs
build regressions
that 0.2.1 introduced
on 32-bit and <code class="language-plaintext highlighter-rouge">no_std</code> embedded targets
and in the <code class="language-plaintext highlighter-rouge">verify</code>-without-<code class="language-plaintext highlighter-rouge">floats</code> feature combination.
The pattern is consolidation
and tooling maturation,
not a change of direction.
The direction is set
by the 0.3.0 self-hosted-compiler goal
that the 0.2.2 scaffold
lays the groundwork for.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://crates.io/crates/keleusma">Keleusma, Crate on crates.io</a></li>
  <li><a href="https://crates.io/crates/keleusma-cli">Keleusma, Command-Line Crate on crates.io</a></li>
  <li><a href="https://docs.rs/keleusma/0.2.2">Keleusma, Application-Programming-Interface Documentation on docs.rs</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/v0.2.2/examples/scripts">Keleusma, Example Scripts</a></li>
  <li><a href="https://github.com/sgeos/keleusma">Keleusma, GitHub Repository</a></li>
  <li><a href="https://sgeos.github.io/keleusma/">Keleusma, Hosted Book (mdbook)</a></li>
  <li><a href="https://sgeos.github.io/keleusma/playground/">Keleusma, Browser-Based Playground</a></li>
  <li><a href="https://sgeos.github.io/keleusma/02_installing_and_running.html">Keleusma, Guide, Installing and Running</a></li>
  <li><a href="https://sgeos.github.io/keleusma/23_big_numbers.html">Keleusma, Guide, Handling Partial Operations</a></li>
  <li><a href="https://sgeos.github.io/keleusma/24_information_flow_labels.html">Keleusma, Guide, Information-Flow Labels</a></li>
  <li><a href="https://sgeos.github.io/keleusma/AUTOMATION_SCRIPTING.html">Keleusma, Reference, Automation and Scripting</a></li>
  <li><a href="https://sgeos.github.io/keleusma/SECURITY_POLICY.html">Keleusma, Reference, Security Policy</a></li>
  <li><a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">Related Post, Getting Started with Keleusma 0.1.1</a></li>
  <li><a href="/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html">Related Post, Getting Started with Keleusma 0.2.0</a></li>
  <li><a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">Related Post, A Verifiable Control Kernel in Keleusma</a></li>
  <li><a href="/security/rust/programming/2026/05/29/information_flow_control_deep_dive_with_keleusma.html">Related Post, Information-Flow Control, A Deep Dive with Keleusma</a></li>
  <li><a href="/compilers/streaming/series/2026/04/17/stream_processor_as_compiler_and_compiler_as_stream_processor.html">Related Post, Streaming Compilers Series Conclusion</a></li>
  <li><a href="/hdl/hardware/self-hosting/2026/07/09/self_hosted_silicon_compiler.html">Related Post, The Self-Hosted Silicon Compiler</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="embedded" /><category term="programming" /></entry><entry><title type="html">Information-Flow Control, A Deep Dive with Keleusma</title><link href="https://sgeos.github.io/security/rust/programming/2026/05/29/information_flow_control_deep_dive_with_keleusma.html" rel="alternate" type="text/html" title="Information-Flow Control, A Deep Dive with Keleusma" /><published>2026-05-29T09:00:00+00:00</published><updated>2026-05-29T09:00:00+00:00</updated><id>https://sgeos.github.io/security/rust/programming/2026/05/29/information_flow_control_deep_dive_with_keleusma</id><content type="html" xml:base="https://sgeos.github.io/security/rust/programming/2026/05/29/information_flow_control_deep_dive_with_keleusma.html"><![CDATA[<!-- A111 -->
<script>console.log("A111");</script>

<p>Most programmers have met access control.
A file has permissions, a database row has an owner,
an endpoint checks a token.
Access control answers one question,
namely who is allowed to read a piece of data.
It says nothing about a second question
that matters just as much,
namely where that data is allowed to go
after an authorized party has read it.
A function that is permitted to read a password
is not thereby permitted to write that password
into a log file, a response body, or an analytics event.
Governing that second question is the job of
<a href="https://en.wikipedia.org/wiki/Taint_checking">information-flow control</a>, or IFC,
and it is a tool most working programmers have never used.</p>

<p>This article is a deep dive.
It covers the theory of IFC from the ground up,
then explains what a first-class language feature
can do that the usual alternatives cannot,
and ends with a mechanical section
on doing the heavy lifting in
<a href="https://github.com/sgeos/keleusma">Keleusma</a>,
a total functional language whose 0.2.0 release
makes information-flow labels part of the type system.
The Keleusma examples were run with the version
shown in the Software Versions section,
and the output shown is the actual output.
A getting-started tour of the language is available in
<a href="/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html">a companion article</a>,
and a worked use of IFC as a governance gate appears in
<a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">the control-kernel article</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-05-29 09:00:00 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026<span class="p">;</span> root:xnu-12377.121.6~2/RELEASE_ARM64_T6000 arm64

<span class="c"># Keleusma</span>
<span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.0
</code></pre></div></div>

<h2 id="the-theory">The Theory</h2>

<h3 id="access-control-stops-at-the-door">Access Control Stops at the Door</h3>

<p>Access control is a gate at the door.
It decides whether a principal may read or write a resource.
Once the principal is inside with the data in hand,
access control has nothing more to say.
The data can be copied, transformed, combined with other data,
and written anywhere the principal can write.
The classic failure is the program that is authorized
to read a secret and also authorized to write a public channel,
and that, through a bug, copies the one into the other.
No access-control check is violated.
The leak happens entirely on the authorized side of the gate.</p>

<p>Information-flow control governs the propagation of data
rather than the act of reading it.
It tracks where information goes,
and it enforces a policy on the paths data may take
from its sources to its sinks.</p>

<h3 id="labels-and-the-lattice">Labels and the Lattice</h3>

<p>The foundational model is due to
<a href="https://dl.acm.org/doi/10.1145/360051.360056">Dorothy Denning (1976)</a>.
Each piece of data carries a security class, or label,
and the labels form a lattice,
a partial order with a least upper bound for any pair.
A simple lattice has two points,
public below secret.
A realistic system has many,
one per compartment or category of sensitivity,
and the order captures which classes are more restrictive.
The rule of secure flow is then stated on the lattice.
Information may flow from a lower class to an equal or higher class,
never from a higher class to a lower one.
Combining two values yields a value
labelled with the least upper bound of their labels,
so a computation that touches a secret produces a secret.
<a href="https://dl.acm.org/doi/10.1145/359636.359712">Denning and Denning (1977)</a>
showed that a compiler could certify these flows mechanically.
<a href="https://en.wikipedia.org/wiki/Lattice-based_access_control">Lattice-based access control</a>
is the same lattice applied to the access question.</p>

<h3 id="noninterference">Noninterference</h3>

<p>The formal goal that IFC aims at is
<a href="https://en.wikipedia.org/wiki/Non-interference_(security)">noninterference</a>,
introduced by <a href="https://www.cs.purdue.edu/homes/ninghui/readings/AccessControl/goguen_meseguer_82.pdf">Goguen and Meseguer (1982)</a>.
A system is noninterfering when its public outputs
do not depend on its secret inputs.
Vary the secret inputs however you like,
and an observer of the public outputs sees no difference.
If that holds, no information about the secrets
can be inferred from the public behavior,
which is exactly the confidentiality property wanted.
Noninterference is the yardstick against which
an information-flow mechanism is judged sound.</p>

<h3 id="explicit-and-implicit-flows">Explicit and Implicit Flows</h3>

<p>There are two ways information moves,
and the second is the one that catches people out.
An explicit flow is a direct assignment of data,
copying a secret into a public variable.
An implicit flow carries information through control flow.
Branch on a secret, and the path taken reveals something about it,
so any public value written differently on the two branches
leaks a bit of the secret without ever copying it directly.
The work of
<a href="https://people.mpi-sws.org/~dg/teaching/lis2014/modules/ifc-1-volpano96.pdf">Volpano, Smith, and Irvine (1996)</a>
recast Denning’s analysis as a type system
and proved that a well-typed program is noninterfering,
which requires accounting for both kinds of flow.
The survey by
<a href="https://www.cs.cornell.edu/andru/papers/jsac/sm-jsac03.pdf">Sabelfeld and Myers (2003)</a>
is the standard map of the field that grew from this.</p>

<h3 id="static-versus-dynamic-enforcement">Static Versus Dynamic Enforcement</h3>

<p>An information-flow policy can be enforced at run time
or before the program runs.
Dynamic enforcement, the family that includes
<a href="https://en.wikipedia.org/wiki/Taint_checking">taint tracking</a>,
attaches a taint bit to values as the program executes
and checks it at sinks.
Static enforcement, the language-based approach,
encodes labels in the type system
and rejects an offending program at compile time.
<a href="https://www.cs.cornell.edu/jif/">Jif</a>, the information-flow extension of Java
built on the decentralized label model of
<a href="https://www.cs.cornell.edu/andru/papers/iflow-tosem.pdf">Myers and Liskov</a>,
is the best-known static system.
Keleusma is a static, type-based system in this lineage.</p>

<h3 id="declassification">Declassification</h3>

<p>A policy of pure noninterference is too strict to be useful.
A password checker must reveal one bit,
namely whether the password matched.
A statistics service must reveal an aggregate
computed from private records.
Every real system deliberately releases some information,
and the controlled release is called declassification.
Declassification is also the dangerous part,
because it is the one place the noninterference guarantee is broken on purpose,
and an attacker who can influence it can widen the leak.
<a href="https://www.cse.chalmers.se/~andrei/sabelfeld-sands-jcs07.pdf">Sabelfeld and Sands (2009)</a>
organized the question into dimensions,
namely what is released, who releases it,
where in the system, and when.
A sound IFC design makes declassification rare, explicit, and auditable.</p>

<p>A first Keleusma example shows a labelled value.
The operator <code class="language-plaintext highlighter-rouge">classify</code> attaches a label,
written after the value with an <code class="language-plaintext highlighter-rouge">@</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="nd">@Master</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Master</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 01_classify.kel
42
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Word@Master</code> is a <code class="language-plaintext highlighter-rouge">Word</code> carrying the label <code class="language-plaintext highlighter-rouge">Master</code>.
In the lattice, an unlabelled value is the public bottom,
and adding a label moves up to a more restrictive class.
The label exists only during checking.
It is erased before the program runs and costs nothing at run time.</p>

<h2 id="what-first-class-ifc-can-do-that-other-approaches-cannot">What First-Class IFC Can Do That Other Approaches Cannot</h2>

<p>A team that wants to control information flow today
reaches for one of a few tools.
Each falls short in a way a first-class language feature does not.</p>

<h3 id="it-catches-implicit-flows">It Catches Implicit Flows</h3>

<p>Taint-tracking libraries, the most common approach,
follow explicit flows well.
They tend to miss implicit flows,
because tracking information through control flow at run time
is expensive and is often skipped.
A type-based system checks every path at compile time.
Here a public output is computed on two branches
selected by a secret, an implicit flow that copies nothing.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">broadcast</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">1</span><span class="nd">@Master</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">derived</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">secret</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">200</span><span class="w"> </span><span class="p">};</span><span class="w">
    </span><span class="n">broadcast</span><span class="p">(</span><span class="n">derived</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 04_implicit.kel
error: compile: 8:15: <span class="nb">type </span>error: argument to <span class="sb">`</span>broadcast<span class="sb">`</span> expects Word, got Word@Master
</code></pre></div></div>

<p>The comparison <code class="language-plaintext highlighter-rouge">secret &gt; 0</code> produces a labelled boolean,
the <code class="language-plaintext highlighter-rouge">if</code> over a labelled condition produces a labelled result,
and the attempt to broadcast that result is rejected
before the program runs.
A label in Keleusma is not a fence at a single point.
It is a dye that stains every value derived from a labelled one,
through arithmetic, comparison, and branching alike.</p>

<h3 id="it-costs-nothing-at-run-time">It Costs Nothing at Run Time</h3>

<p>Dynamic taint tracking pays for itself on every operation,
carrying and checking taint bits as the program runs.
Keleusma labels are part of the type
and are erased after checking,
so the running program is identical to one written without labels.
The guarantee is bought entirely at compile time.</p>

<h3 id="it-cannot-be-bypassed-by-convention">It Cannot Be Bypassed by Convention</h3>

<p>A common lightweight approach wraps secret data in a newtype,
a <code class="language-plaintext highlighter-rouge">Secret&lt;T&gt;</code> that the type checker will not let you pass
where a plain <code class="language-plaintext highlighter-rouge">T</code> is expected.
This stops one mistake, the direct substitution,
but the protection ends the moment the value is unwrapped.
A newtype tracks nothing through a value derived by unwrapping,
computing, and rewrapping, and nothing through control flow.
It is a fence at the wrapper boundary, not a property of the data.
A first-class label propagates through the computation,
so the only way out is the explicit release operator,
not an unwrap method that any caller can invoke.</p>

<h3 id="it-makes-every-release-auditable">It Makes Every Release Auditable</h3>

<p>Manual code review and scattered sanitization
depend on a human noticing every path.
Humans miss implicit flows and derived leaks reliably.
With a first-class feature, the release of confidential data
happens at exactly one operator, <code class="language-plaintext highlighter-rouge">declassify</code>,
and a reviewer finds every release by searching for that one word.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">broadcast</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">take</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Master</span><span class="p">;</span><span class="w">
    </span><span class="n">broadcast</span><span class="p">(</span><span class="nd">declassify</span><span class="w"> </span><span class="n">take</span><span class="nd">@Master</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 03_declassify.kel
42
</code></pre></div></div>

<p>Remove the <code class="language-plaintext highlighter-rouge">declassify</code>, and the same program is rejected,
because the labelled value reaches a plain <code class="language-plaintext highlighter-rouge">Word</code> parameter.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 02_leak.kel
error: compile: 7:15: <span class="nb">type </span>error: argument to <span class="sb">`</span>broadcast<span class="sb">`</span> expects Word, got Word@Master
</code></pre></div></div>

<p>The contrast with access control is worth restating.
Access control would have permitted both the read and the write.
The leak is on the authorized side of the gate,
and only a flow-aware check sees it.</p>

<h2 id="the-mechanical-section">The Mechanical Section</h2>

<p>This section is the working reference
for doing IFC heavy lifting with the Keleusma grammar.
The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/24_information_flow_labels.md">information-flow chapter of the guide</a>
covers the same surface for the script author.</p>

<h3 id="attaching-a-label">Attaching a Label</h3>

<p><code class="language-plaintext highlighter-rouge">classify expr@Label</code> adds a label to a value.
Adding a label only tightens flow restrictions,
so it is always admitted.
A label set is written in braces,
and the empty set is the public bottom of the lattice.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">store</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="nd">@{Secret, Pii}</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="nd">@{Secret, Pii}</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="nd">@{Secret, Pii}</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">record</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">7</span><span class="nd">@{Secret, Pii}</span><span class="p">;</span><span class="w">
    </span><span class="n">store</span><span class="p">(</span><span class="n">record</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 05_label_set.kel
7
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Word@{Secret, Pii}</code> carries two labels.
The labels are user-defined names, and their meaning is the programmer’s.
More labels mean a more restrictive class,
which is the powerset lattice of the chosen label names.</p>

<h3 id="labels-ride-on-types-and-propagate">Labels Ride on Types and Propagate</h3>

<p>A label is part of the type, written <code class="language-plaintext highlighter-rouge">T@Label</code>.
The type checker propagates it through operations,
so the result of any expression that touches a labelled value
is itself labelled, as the implicit-flow example showed.
This is what makes the guarantee hold without the programmer
threading the label through by hand.</p>

<h3 id="boundaries-are-where-flow-is-checked">Boundaries Are Where Flow Is Checked</h3>

<p>A flow is checked when a value crosses a boundary.
The boundaries are function parameter and return types,
<code class="language-plaintext highlighter-rouge">shared</code> data fields that the host reads and writes,
and <code class="language-plaintext highlighter-rouge">private</code> data fields that survive across a yield and resume.
A plain <code class="language-plaintext highlighter-rouge">Word</code> parameter accepts only an unlabelled <code class="language-plaintext highlighter-rouge">Word</code>,
so handing it a <code class="language-plaintext highlighter-rouge">Word@Master</code> is the rejected leak seen above.
Declaring a parameter or return with a label
states the policy at that boundary directly.</p>

<h3 id="releasing-a-label">Releasing a Label</h3>

<p><code class="language-plaintext highlighter-rouge">declassify expr@Label</code> removes a label.
It is always admitted, but it is the one visible point
where confidential data is released,
and it is the place a review process must scrutinize,
consistent with the dimensions of
<a href="https://www.cse.chalmers.se/~andrei/sabelfeld-sands-jcs07.pdf">Sabelfeld and Sands</a>.
Keep declassification rare and push it to a single chokepoint.
A label set is released the same way,
naming the labels to remove.</p>

<h3 id="negative-labels-at-boundaries">Negative Labels at Boundaries</h3>

<p>The positive label says what a value carries.
The negative label, written with a <code class="language-plaintext highlighter-rouge">!</code> prefix,
says what a boundary refuses.
A parameter typed <code class="language-plaintext highlighter-rouge">Word@!Secret</code> accepts any value
that does not carry the <code class="language-plaintext highlighter-rouge">Secret</code> label.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">publish</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="nd">@!Secret</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">5</span><span class="nd">@Public</span><span class="p">;</span><span class="w">
    </span><span class="n">publish</span><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 06_negative.kel
5
</code></pre></div></div>

<p>A value that does carry the forbidden label is rejected,
with a diagnostic that names the offending label.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 06b_negative_reject.kel
error: compile: 7:13: <span class="nb">type </span>error: argument 1 to <span class="sb">`</span>publish<span class="sb">`</span> carries the label <span class="sb">`</span>Secret<span class="sb">`</span> which the parameter<span class="s1">'s `!`-prefix declaration forbids
</span></code></pre></div></div>

<p>Negative labels are admitted only at boundary positions,
namely function parameters and returns and the <code class="language-plaintext highlighter-rouge">shared</code> and <code class="language-plaintext highlighter-rouge">private</code>
data fields, and only at the top level of a type,
not nested inside a tuple, array, or option.
A single position may not mix positive and negative labels.
Negative labels are a boundary clause rather than a propagating type,
so unlike positive labels they do not stain derived values.</p>

<h3 id="a-working-pattern">A Working Pattern</h3>

<p>The pattern that does most of the work is small.
Classify confidential data at its source.
Let the labels propagate untouched through the computation.
Type every output boundary so it refuses the labels,
either by leaving the boundary unlabelled
so it accepts only public data,
or by giving it a negative label that names what it forbids.
Release through a single <code class="language-plaintext highlighter-rouge">declassify</code> at one reviewed chokepoint.
The compiler then proves, before the program runs,
that no other path carries confidential data to an output.</p>

<h2 id="honest-limitations">Honest Limitations</h2>

<p>A few boundaries of the Keleusma model are worth stating plainly.
The labels are user-defined and abstract.
The language enforces the lattice discipline,
but the meaning of each label, and whether it models
confidentiality or integrity, is a convention the programmer supplies.
Positive labels propagate through values,
including through branches, which is the strong part.
Negative labels are checked only at boundaries
and do not propagate through derived values in the 0.2.x line,
so they express what a position refuses rather than tracking absence
through a computation.
And declassification remains trusted.
The compiler guarantees that release happens only at a <code class="language-plaintext highlighter-rouge">declassify</code>,
but it cannot judge whether a given release is wise.
That judgment is the reviewer’s,
which is the reason the operator is made so visible.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Information-flow control answers a question access control cannot,
namely where data may go after it is read.
The theory is fifty years old,
from Denning’s lattice through noninterference
to the language-based type systems that enforce it,
yet it remains absent from most working programmers’ toolkits.
A first-class language feature catches the implicit flows
that taint libraries miss, costs nothing at run time,
cannot be bypassed by unwrapping a value,
and reduces every release of a secret to one auditable keyword.
Keleusma 0.2.0 puts that feature in the type system,
where the guarantee is proved before the program runs.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://www.cs.cornell.edu/jif/">Reference, Jif, Java Information Flow</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Lattice-based_access_control">Reference, Lattice-Based Access Control</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Non-interference_(security)">Reference, Noninterference, Security</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Taint_checking">Reference, Taint Checking</a></li>
  <li><a href="https://github.com/sgeos/keleusma">Keleusma, GitHub Repository</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/24_information_flow_labels.md">Keleusma, Guide Chapter 24, Information-Flow Labels</a></li>
  <li><a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">Related Post, A Verifiable Control Kernel in Keleusma</a></li>
  <li><a href="/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html">Related Post, Getting Started with Keleusma 0.2.0</a></li>
  <li><a href="https://dl.acm.org/doi/10.1145/359636.359712">Research, Certification of Programs for Secure Information Flow</a></li>
  <li><a href="https://www.cse.chalmers.se/~andrei/sabelfeld-sands-jcs07.pdf">Research, Declassification, Dimensions and Principles</a></li>
  <li><a href="https://www.cs.cornell.edu/andru/papers/jsac/sm-jsac03.pdf">Research, Language-Based Information-Flow Security</a></li>
  <li><a href="https://dl.acm.org/doi/10.1145/360051.360056">Research, A Lattice Model of Secure Information Flow</a></li>
  <li><a href="https://www.cs.cornell.edu/andru/papers/iflow-tosem.pdf">Research, Protecting Privacy Using the Decentralized Label Model</a></li>
  <li><a href="https://www.cs.purdue.edu/homes/ninghui/readings/AccessControl/goguen_meseguer_82.pdf">Research, Security Policies and Security Models</a></li>
  <li><a href="https://people.mpi-sws.org/~dg/teaching/lis2014/modules/ifc-1-volpano96.pdf">Research, A Sound Type System for Secure Flow Analysis</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="security" /><category term="rust" /><category term="programming" /></entry><entry><title type="html">Getting Started with Keleusma 0.2.0</title><link href="https://sgeos.github.io/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html" rel="alternate" type="text/html" title="Getting Started with Keleusma 0.2.0" /><published>2026-05-28T09:00:00+00:00</published><updated>2026-05-28T09:00:00+00:00</updated><id>https://sgeos.github.io/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started</id><content type="html" xml:base="https://sgeos.github.io/rust/embedded/programming/2026/05/28/keleusma_0_2_0_getting_started.html"><![CDATA[<!-- A110 -->
<script>console.log("A110");</script>

<p><a href="https://github.com/sgeos/keleusma">Keleusma</a> is a total functional stream-processing language
that compiles to bytecode and runs on a stack-based virtual machine.
It is an embedded language in the sense described in
<a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/01_what_keleusma_is.md">the first chapter of its guide</a>.
A Keleusma program is not a whole application.
It is the small, exact, predictable part inside a larger host program,
and it is built so that several things can be proved
before the program ever runs, namely that every turn finishes
within a bounded amount of time and a bounded amount of memory.
This makes it a fit for embedded targets,
real-time audio, game logic, and any setting
where deterministic, bounded execution is a hard requirement.</p>

<p>Version 0.2.0 is the first publicly released line.
It introduces cryptographic module signing,
<a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/24_information_flow_labels.md">information-flow labels</a>,
<a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/22_newtypes_and_refinement.md">newtypes with refinement predicates</a>,
and a reset instruction-set architecture.
This article is a practical tour of the 0.2.0 release.
Every code listing below was run with the version shown
in the Software Versions section, and the output shown
is the actual output produced.
This article is an on-ramp, not a complete reference.
The language ships with a
<a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/README.md">forty-chapter guide</a>
that teaches Keleusma from first principles using music as its on-ramp,
and a separate embedding track for Rust hosts.
Readers who want the deep dive should start there.
A previous article covered
<a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">the earlier 0.1.1 pre-release</a>,
and a companion article uses Keleusma to build
<a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">a verifiable control kernel</a>.</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-05-28 09:00:00 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026<span class="p">;</span> root:xnu-12377.121.6~2/RELEASE_ARM64_T6000 arm64

<span class="c"># Keleusma</span>
<span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.0
</code></pre></div></div>

<h2 id="installation">Installation</h2>

<p>Keleusma is published on <a href="https://crates.io/crates/keleusma">crates.io</a> as a library,
with the command-line tool distributed as a separate crate,
<a href="https://crates.io/crates/keleusma-cli"><code class="language-plaintext highlighter-rouge">keleusma-cli</code></a>.
The source lives on <a href="https://github.com/sgeos/keleusma">GitHub</a>,
and the API documentation is on <a href="https://docs.rs/keleusma">docs.rs</a>.</p>

<p>The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/02_installing_and_running.md">installation chapter</a> documents the canonical path,
which is to install the command-line tool from a source checkout.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/sgeos/keleusma
<span class="nb">cd </span>keleusma
cargo <span class="nb">install</span> <span class="nt">--path</span> keleusma-cli <span class="nt">--bin</span> keleusma
</code></pre></div></div>

<p>Confirm the installation.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.0
</code></pre></div></div>

<p>To embed the runtime in a Rust program rather than use the tool,
add the library crate to a project instead.</p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">keleusma</span> <span class="p">=</span> <span class="s">"0.2"</span>
</code></pre></div></div>

<h2 id="a-first-program">A First Program</h2>

<p>The smallest unit of Keleusma is the <code class="language-plaintext highlighter-rouge">fn</code>, an atomic total function.
Atomic means it runs start to finish without pausing.
Total means it always finishes.
A <code class="language-plaintext highlighter-rouge">fn</code> takes its inputs, computes, and returns a result.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">n</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">n</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">double</span><span class="p">(</span><span class="mi">21</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The command-line tool calls <code class="language-plaintext highlighter-rouge">main</code> and prints the returned value.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 01_first_program.kel
42
</code></pre></div></div>

<h2 id="values-and-types">Values and Types</h2>

<p>Keleusma has a small set of primitive types.
<code class="language-plaintext highlighter-rouge">Word</code> is the machine integer and is signed.
<code class="language-plaintext highlighter-rouge">Byte</code>, <code class="language-plaintext highlighter-rouge">Fixed</code>, and <code class="language-plaintext highlighter-rouge">Float</code> cover byte-width integers,
fixed-point numbers, and IEEE 754 floating point.
<code class="language-plaintext highlighter-rouge">bool</code> is a truth value, and <code class="language-plaintext highlighter-rouge">Text</code> is textual data.
A binding is introduced with <code class="language-plaintext highlighter-rouge">let</code> and is immutable.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">count</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">6</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">factor</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">enabled</span><span class="p">:</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="n">enabled</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">count</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">factor</span><span class="w">
    </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">count</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">factor</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 02_values.kel
42
</code></pre></div></div>

<p>A function may return <code class="language-plaintext highlighter-rouge">Text</code>, and the tool prints it directly.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Text</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="s2">"Keleusma"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 03_text.kel
Keleusma
</code></pre></div></div>

<h2 id="making-decisions">Making Decisions</h2>

<p>Keleusma has <code class="language-plaintext highlighter-rouge">if</code> and <code class="language-plaintext highlighter-rouge">else</code>, and it has <code class="language-plaintext highlighter-rouge">match</code> for pattern dispatch
over the variants of an <code class="language-plaintext highlighter-rouge">enum</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span><span class="w"> </span><span class="nc">Signal</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nc">Red</span><span class="p">,</span><span class="w">
    </span><span class="nc">Yellow</span><span class="p">,</span><span class="w">
    </span><span class="nc">Green</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">delay</span><span class="p">(</span><span class="n">s</span><span class="p">:</span><span class="w"> </span><span class="nc">Signal</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">match</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nc">Signal</span><span class="o">::</span><span class="nc">Red</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w">
        </span><span class="nc">Signal</span><span class="o">::</span><span class="nc">Yellow</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
        </span><span class="nc">Signal</span><span class="o">::</span><span class="nc">Green</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">delay</span><span class="p">(</span><span class="nc">Signal</span><span class="o">::</span><span class="nc">Red</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">delay</span><span class="p">(</span><span class="nc">Signal</span><span class="o">::</span><span class="nc">Green</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 04_decisions.kel
55
</code></pre></div></div>

<h2 id="bounded-repetition">Bounded Repetition</h2>

<p>Keleusma has <code class="language-plaintext highlighter-rouge">for</code>, and every repetition has a count
that is known before the loop begins.
There are no unbounded loops, and there is no recursion.
These omissions are the price of the bounded-execution promise.
Because local bindings are immutable, a <code class="language-plaintext highlighter-rouge">for</code> loop inside a <code class="language-plaintext highlighter-rouge">fn</code>
does not accumulate a result. Iteration that updates state
belongs in the <code class="language-plaintext highlighter-rouge">loop</code> and <code class="language-plaintext highlighter-rouge">yield</code> function categories,
which are covered below. The example returns a value
computed by direct indexing.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">steps</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="mi">16</span><span class="p">];</span><span class="w">
    </span><span class="k">for</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">steps</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="k">let</span><span class="w"> </span><span class="n">_scaled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">10</span><span class="p">;</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">4</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="k">let</span><span class="w"> </span><span class="n">_i2</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">i</span><span class="p">;</span><span class="w">
    </span><span class="p">}</span><span class="w">
    </span><span class="n">steps</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">steps</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 05_repetition.kel
18
</code></pre></div></div>

<h2 id="structs">Structs</h2>

<p>A <code class="language-plaintext highlighter-rouge">struct</code> groups named fields.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span><span class="w"> </span><span class="nc">Note</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">pitch</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w">
    </span><span class="n">velocity</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">loudness</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="nc">Note</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">n</span><span class="p">.</span><span class="n">pitch</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">n</span><span class="p">.</span><span class="n">velocity</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">middle_c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Note</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">pitch</span><span class="p">:</span><span class="w"> </span><span class="mi">60</span><span class="p">,</span><span class="w"> </span><span class="n">velocity</span><span class="p">:</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">};</span><span class="w">
    </span><span class="n">loudness</span><span class="p">(</span><span class="n">middle_c</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 06_structs.kel
160
</code></pre></div></div>

<h2 id="multiheaded-functions-and-guards">Multiheaded Functions and Guards</h2>

<p>A function name may have several heads, each with its own
parameter pattern. The runtime tries the heads in source order
and dispatches to the first one whose pattern matches.
A head may carry a <code class="language-plaintext highlighter-rouge">when</code> guard after its return type.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w">        </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w">           </span><span class="p">{</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w">  </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="k">when</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w">  </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w">           </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nd">classify</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">0</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The first call matches the literal head and returns 100.
The second matches the guarded head and returns 1.
The third fails the guard and falls to the final head, returning 0.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 07_multiheaded.kel
101
</code></pre></div></div>

<h2 id="the-pipeline-operator">The Pipeline Operator</h2>

<p>The pipeline operator <code class="language-plaintext highlighter-rouge">|&gt;</code> threads the value on its left
as the first argument to the call on its right.
It reads left to right and reduces nesting in transformation chains.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">inc</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">triple</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="mi">13</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">inc</span><span class="p">()</span><span class="w">
    </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">triple</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 08_pipeline.kel
42
</code></pre></div></div>

<h2 id="traits-and-methods">Traits and Methods</h2>

<p>Methods are declared in a <code class="language-plaintext highlighter-rouge">trait</code> and implemented for concrete types
in <code class="language-plaintext highlighter-rouge">impl</code> blocks. The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/21_generics_and_traits.md">generics and traits chapter</a>
covers the full surface, including generic type parameters with bounds.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">trait</span><span class="w"> </span><span class="nc">Doubler</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="p">;</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="k">impl</span><span class="w"> </span><span class="nc">Doubler</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">21</span><span class="p">;</span><span class="w">
    </span><span class="n">n</span><span class="p">.</span><span class="n">double</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 09_traits.kel
42
</code></pre></div></div>

<h2 id="newtypes-and-refinement-types">Newtypes and Refinement Types</h2>

<p>A <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/22_newtypes_and_refinement.md">newtype</a> gives an underlying type
a distinct name, and it may carry a
<a href="https://en.wikipedia.org/wiki/Refinement_type">refinement</a>, a rule that every value must satisfy.
The rule is an ordinary predicate function, and it is checked
at every construction. A construction that provably breaks the rule
is rejected before the program runs.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">in_range</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">127</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="k">newtype</span><span class="w"> </span><span class="nc">Velocity</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">in_range</span><span class="p">;</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">raw</span><span class="p">(</span><span class="n">v</span><span class="p">:</span><span class="w"> </span><span class="nc">Velocity</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">v</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">Word</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">soft</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Velocity</span><span class="p">(</span><span class="mi">40</span><span class="p">);</span><span class="w">
    </span><span class="n">raw</span><span class="p">(</span><span class="n">soft</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 10_newtype.kel
40
</code></pre></div></div>

<p>The value of the refinement is what it refuses.
A literal that violates the predicate is a compile-time error,
not a runtime surprise.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">raw</span><span class="p">(</span><span class="nc">Velocity</span><span class="p">(</span><span class="mi">200</span><span class="p">))</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 10b_newtype_reject.kel
error: compile: 4:25: refinement check <span class="sb">`</span>in_range<span class="sb">`</span> provably fails <span class="k">for </span>newtype <span class="sb">`</span>Velocity<span class="sb">`</span> at compile <span class="nb">time </span>on argument 200
</code></pre></div></div>

<h2 id="information-flow-labels">Information-Flow Labels</h2>

<p>Version 0.2.0 adds <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/24_information_flow_labels.md">information-flow labels</a>.
A type can carry a label, written <code class="language-plaintext highlighter-rouge">T@Label</code>, that marks a value.
The operator <code class="language-plaintext highlighter-rouge">classify</code> attaches a label,
and <code class="language-plaintext highlighter-rouge">declassify</code> removes one. The language follows the label
and refuses, before the program runs, to let a labelled value
flow into a place that does not accept the label.
A reviewer can find every release of confidential data
by searching for <code class="language-plaintext highlighter-rouge">declassify</code>.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">publish</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Private</span><span class="p">;</span><span class="w">
    </span><span class="n">publish</span><span class="p">(</span><span class="nd">declassify</span><span class="w"> </span><span class="n">secret</span><span class="nd">@Private</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 11_info_flow.kel
42
</code></pre></div></div>

<p>Remove the <code class="language-plaintext highlighter-rouge">declassify</code>, and the leak is proved and rejected.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">secret</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Private</span><span class="p">;</span><span class="w">
    </span><span class="n">publish</span><span class="p">(</span><span class="n">secret</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 11b_info_flow_reject.kel
error: compile: 4:13: <span class="nb">type </span>error: argument to <span class="sb">`</span>publish<span class="sb">`</span> expects Word, got Word@Private
</code></pre></div></div>

<h2 id="checked-arithmetic">Checked Arithmetic</h2>

<p>Keleusma arithmetic on <code class="language-plaintext highlighter-rouge">Word</code> can be matched against
its overflow behavior. The construct binds the result through
an <code class="language-plaintext highlighter-rouge">ok</code> arm when it fits, and through <code class="language-plaintext highlighter-rouge">overflow</code> or <code class="language-plaintext highlighter-rouge">underflow</code> arms
when it does not, each exposing the high and low halves
of the wider intermediate result. This is the building block
for multi-word and big-number arithmetic.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">add_checked</span><span class="p">(</span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nb">ok</span><span class="p">(</span><span class="n">v</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">v</span><span class="p">,</span><span class="w">
        </span><span class="nb">overflow</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">l</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">l</span><span class="p">,</span><span class="w">
        </span><span class="nb">underflow</span><span class="p">(</span><span class="n">_</span><span class="p">,</span><span class="w"> </span><span class="n">l</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">l</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">add_checked</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">22</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 12_checked.kel
42
</code></pre></div></div>

<h2 id="the-three-function-categories">The Three Function Categories</h2>

<p>Every Keleusma function is exactly one of three kinds,
fixed by the word that begins its declaration.
The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/15_three_function_categories.md">function categories chapter</a>
develops this in full.</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">fn</code> is an atomic total function. It runs straight through
and returns. Every example above is an <code class="language-plaintext highlighter-rouge">fn</code>.</li>
  <li><code class="language-plaintext highlighter-rouge">yield</code> is a non-atomic total function. It may pause,
hand a value to the host, and resume when the host resumes it,
and it must eventually finish.</li>
  <li><code class="language-plaintext highlighter-rouge">loop</code> is a productive divergent function. It never finishes
and must hand a value to the host on every cycle.</li>
</ul>

<p>A <code class="language-plaintext highlighter-rouge">yield</code> or <code class="language-plaintext highlighter-rouge">loop</code> program compiles and passes the verifier,
but it is meant to be driven by a host through a call-and-resume
protocol rather than run to a single value. On the 0.2.0
command-line tool, <code class="language-plaintext highlighter-rouge">keleusma run</code> drives <code class="language-plaintext highlighter-rouge">fn</code> programs.
The <code class="language-plaintext highlighter-rouge">yield</code> and <code class="language-plaintext highlighter-rouge">loop</code> programs are driven by an embedding host,
which the next section introduces. The tool can still confirm
that such a program lexes, type-checks, and passes the
bounded-execution verifier by compiling it to bytecode.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">yield</span><span class="w"> </span><span class="n">main</span><span class="p">(</span><span class="n">tick</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">reply</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">yield</span><span class="w"> </span><span class="n">tick</span><span class="p">;</span><span class="w">
    </span><span class="n">reply</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma compile 14_yield.kel <span class="nt">-o</span> out.bin
wrote out.bin <span class="o">(</span>204 bytes<span class="o">)</span>
</code></pre></div></div>

<h2 id="what-the-verifier-guarantees">What the Verifier Guarantees</h2>

<p>Before a program runs, the language proves a worst-case
<a href="https://en.wikipedia.org/wiki/Worst-case_execution_time">execution-time</a> bound and a worst-case memory bound,
and it proves that every <code class="language-plaintext highlighter-rouge">fn</code> finishes.
The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/20_time_and_memory_budgets.md">budgets chapter</a> explains the two budgets.
A program whose bounds cannot be proved is rejected.
Recursion is one such construct, because it admits unbounded depth.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">countdown</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">countdown</span><span class="p">(</span><span class="n">n</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 class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">countdown</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 13_recursion_reject.kel
error: verify: VerifyError<span class="o">(</span><span class="s2">"countdown: recursive call detected during WCMU topological sort"</span><span class="o">)</span>
</code></pre></div></div>

<p>The rejection is the safety property. A program the verifier accepts
is one whose time and memory bounds are proved,
not merely one whose bounds happen to exist.</p>

<h2 id="signed-modules">Signed Modules</h2>

<p>Version 0.2.0 can sign compiled bytecode with Ed25519,
described in the <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/26_signed_modules_and_hot_swap.md">signed modules chapter</a>.
The signature proves origin and integrity for a module
delivered to a device in the field.
A program opts in with the <code class="language-plaintext highlighter-rouge">signed</code> modifier on its entry function.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kr">signed</span><span class="w"> </span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="mi">21</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">21</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Generate a key pair, compile and sign, then run against the public key.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma keygen <span class="nt">--seed</span> seed.bin <span class="nt">--public</span> pub.bin
wrote seed to seed.bin <span class="o">(</span>32 bytes<span class="p">;</span> keep secret<span class="o">)</span>
wrote public key to pub.bin <span class="o">(</span>32 bytes<span class="p">;</span> distribute to verifiers<span class="o">)</span>

<span class="nv">$ </span>keleusma compile signed_demo.kel <span class="nt">--signing-key</span> seed.bin <span class="nt">-o</span> signed_demo.bin
wrote signed_demo.bin <span class="o">(</span>304 bytes<span class="o">)</span>

<span class="nv">$ </span>keleusma run signed_demo.bin <span class="nt">--verifying-key</span> pub.bin
42
</code></pre></div></div>

<p>Run the signed bytecode without the key, and it refuses to load.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run signed_demo.bin
error: load_signed_bytes: LoadError<span class="o">(</span><span class="s2">"bytecode signature did not verify against any registered key"</span><span class="o">)</span>
</code></pre></div></div>

<h2 id="embedding-keleusma-in-rust">Embedding Keleusma in Rust</h2>

<p>The command-line tool is one host. The runtime library is the product,
and the intended use is to embed it in a larger Rust program.
The <a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/EMBEDDING.md">embedding guide</a> documents the full surface.
A minimal host lexes, parses, and compiles a script,
constructs a virtual machine over an arena, and calls it.</p>

<p><code class="language-plaintext highlighter-rouge">Cargo.toml</code></p>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[dependencies]</span>
<span class="py">keleusma</span> <span class="p">=</span> <span class="s">"0.2"</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">src/main.rs</code></p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">compiler</span><span class="p">::</span><span class="n">compile</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">lexer</span><span class="p">::</span><span class="n">tokenize</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">parser</span><span class="p">::</span><span class="n">parse</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">vm</span><span class="p">::{</span><span class="n">Vm</span><span class="p">,</span> <span class="n">VmState</span><span class="p">,</span> <span class="n">DEFAULT_ARENA_CAPACITY</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::{</span><span class="n">Arena</span><span class="p">,</span> <span class="n">Value</span><span class="p">};</span>

<span class="k">const</span> <span class="n">SOURCE</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span> <span class="o">=</span> <span class="s">"fn main() -&gt; Word { 21 + 21 }"</span><span class="p">;</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">tokens</span> <span class="o">=</span> <span class="nf">tokenize</span><span class="p">(</span><span class="n">SOURCE</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"lex error"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">program</span> <span class="o">=</span> <span class="nf">parse</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tokens</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"parse error"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">module</span> <span class="o">=</span> <span class="nf">compile</span><span class="p">(</span><span class="o">&amp;</span><span class="n">program</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"compile error"</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">arena</span> <span class="o">=</span> <span class="nn">Arena</span><span class="p">::</span><span class="nf">with_capacity</span><span class="p">(</span><span class="n">DEFAULT_ARENA_CAPACITY</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">vm</span> <span class="o">=</span> <span class="nn">Vm</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">arena</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"vm construction"</span><span class="p">);</span>

    <span class="k">match</span> <span class="n">vm</span><span class="nf">.call</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[])</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"vm call"</span><span class="p">)</span> <span class="p">{</span>
        <span class="nn">VmState</span><span class="p">::</span><span class="nf">Finished</span><span class="p">(</span><span class="nn">Value</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="nd">println!</span><span class="p">(</span><span class="s">"script returned {n}"</span><span class="p">),</span>
        <span class="n">other</span> <span class="k">=&gt;</span> <span class="nd">println!</span><span class="p">(</span><span class="s">"unexpected VM state: {:?}"</span><span class="p">,</span> <span class="n">other</span><span class="p">),</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>cargo run <span class="nt">--quiet</span>
script returned 42
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Vm::call</code> starts execution and returns a <code class="language-plaintext highlighter-rouge">VmState</code>.
An <code class="language-plaintext highlighter-rouge">fn</code> program returns <code class="language-plaintext highlighter-rouge">VmState::Finished</code>.
A <code class="language-plaintext highlighter-rouge">yield</code> program returns <code class="language-plaintext highlighter-rouge">VmState::Yielded</code>, and the host
calls <code class="language-plaintext highlighter-rouge">Vm::resume</code> with a value to continue.
A <code class="language-plaintext highlighter-rouge">loop</code> program yields on every cycle and resets at the end of its body,
where a host may hot-swap the module.
This call-and-resume protocol is how a host drives
the <code class="language-plaintext highlighter-rouge">yield</code> and <code class="language-plaintext highlighter-rouge">loop</code> programs from the previous sections.</p>

<h2 id="the-interactive-prompt">The Interactive Prompt</h2>

<p>The tool also provides a read-evaluate-print loop
for trying expressions interactively.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma repl
</code></pre></div></div>

<h2 id="going-deeper">Going Deeper</h2>

<p>This tour covers enough to read and write Keleusma programs,
but it is deliberately shallow. The
<a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/README.md">forty-chapter guide</a> is the place to go next.
Its learner track teaches the language from first principles,
and its embedding track addresses Rust hosts in depth.
The <a href="https://github.com/sgeos/keleusma/blob/main/docs/spec/INSTRUCTION_SET.md">instruction-set reference</a> documents the bytecode,
and the <a href="https://github.com/sgeos/keleusma/tree/main/examples/scripts">example scripts</a> in the repository
are the seed material the guide builds on.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Keleusma 0.2.0 trades the unbounded constructs of a general-purpose
language for a promise that no other small scripting language makes,
namely that every program it accepts is proved, before it runs,
to finish within bounded time and bounded memory.
Around that core it adds refinement types, information-flow labels,
and signed modules, and it is built to be embedded in a Rust host.
For a script author, the path forward is the learner track of the guide.
For a systems programmer, it is the embedding track.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://crates.io/crates/keleusma">Keleusma, Crate on crates.io</a></li>
  <li><a href="https://crates.io/crates/keleusma-cli">Keleusma, Command-Line Crate on crates.io</a></li>
  <li><a href="https://docs.rs/keleusma">Keleusma, API Documentation on docs.rs</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/EMBEDDING.md">Keleusma, Embedding Guide</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/main/examples/scripts">Keleusma, Example Scripts</a></li>
  <li><a href="https://github.com/sgeos/keleusma">Keleusma, GitHub Repository</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/README.md">Keleusma, Guide</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/01_what_keleusma_is.md">Keleusma, Guide Chapter 1, What Keleusma Is</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/02_installing_and_running.md">Keleusma, Guide Chapter 2, Installing and Running</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/15_three_function_categories.md">Keleusma, Guide Chapter 15, The Three Function Categories</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/20_time_and_memory_budgets.md">Keleusma, Guide Chapter 20, Time and Memory Budgets</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/21_generics_and_traits.md">Keleusma, Guide Chapter 21, Generics and Traits</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/22_newtypes_and_refinement.md">Keleusma, Guide Chapter 22, Newtypes and Refinement Types</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/24_information_flow_labels.md">Keleusma, Guide Chapter 24, Information-Flow Labels</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/guide/26_signed_modules_and_hot_swap.md">Keleusma, Guide Chapter 26, Signed Modules and Hot Code Swap</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/main/docs/spec/INSTRUCTION_SET.md">Keleusma, Instruction Set Reference</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Refinement_type">Reference, Refinement Type</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Worst-case_execution_time">Reference, Worst-Case Execution Time</a></li>
  <li><a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">Related Post, Getting Started with Keleusma 0.1.1</a></li>
  <li><a href="/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html">Related Post, A Verifiable Control Kernel in Keleusma</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="embedded" /><category term="programming" /></entry><entry><title type="html">A Verifiable Control Kernel in Keleusma for a Truthful-Machine Architecture</title><link href="https://sgeos.github.io/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html" rel="alternate" type="text/html" title="A Verifiable Control Kernel in Keleusma for a Truthful-Machine Architecture" /><published>2026-05-27T09:00:00+00:00</published><updated>2026-05-27T09:00:00+00:00</updated><id>https://sgeos.github.io/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma</id><content type="html" xml:base="https://sgeos.github.io/ai/rust/programming/2026/05/27/verifiable_control_kernel_in_keleusma.html"><![CDATA[<!-- A109 -->
<script>console.log("A109");</script>

<p>A companion article argued that an assistant
that adheres to the scientific method,
values truthfulness over agreement,
and declines when declining is honest
cannot be a single large language model,
and must instead be a compound system
in which the model proposes
and a deterministic, auditable layer
holds the guarantees the model cannot.
That argument is developed in
<a href="/ai/philosophy/2026/05/26/neurosymbolic_blueprint_for_truthful_machines.html">the truthful-machine blueprint</a>.
This article takes the one layer of that design
that can be written as ordinary, verifiable code,
namely the control-and-governance kernel,
and implements its skeleton in
<a href="https://github.com/sgeos/keleusma">Keleusma</a>,
a total functional language
whose verifier proves bounded execution
before a program ever runs.
The Keleusma language itself is introduced in
<a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">an earlier getting-started article</a>.</p>

<p>A statement of scope is necessary first,
because the subject of the blueprint
is a machine that does not overclaim.
The examples below implement only the kernel.
The proposer, the critic ensemble,
the retrieval store, the calibration head,
and the formal verifier of the blueprint
are not written here and cannot be written in Keleusma.
They are large or nondeterministic components
that live in the host program,
and they appear in this article only as
host-supplied inputs and external natives.
Nothing here demonstrates a working truthful machine.
What it demonstrates is that the kernel
that would orchestrate and constrain such a machine
can be expressed in a form
that is proved bounded and total before it runs.
That, and only that, is the claim.</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-05-27 09:00:00 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 25.5.0: Mon Apr 27 20:38:56 PDT 2026<span class="p">;</span> root:xnu-12377.121.6~2/RELEASE_ARM64_T6000 arm64

<span class="c"># Keleusma</span>
<span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.2.1
</code></pre></div></div>

<p>The call-yield-resume driver used in the final section
arrived in the 0.2.1 release. Everything else in this article
also runs unchanged on 0.2.0.</p>

<h2 id="why-this-layer-fits-keleusma">Why This Layer Fits Keleusma</h2>

<p>The blueprint’s central principle is that rigidity
must live in deterministic machinery
outside the stochastic model.
Keleusma is built on the same separation.
A Keleusma program is the score,
and the larger host program is the orchestra.
The score does a small, bounded amount of work,
hands control back, and waits.
The properties Keleusma proves before a program runs,
<a href="https://github.com/sgeos/keleusma/tree/master/docs/guide">totality and bounded worst-case execution time
and memory</a>,
are exactly the properties a control kernel needs
if its behavior is to be audited
rather than trusted.
The verifier rejects by default
anything it cannot prove bounded,
which is the same default-deny posture
the governance layer of the blueprint requires.</p>

<p>The sections that follow build the kernel
one piece at a time.
Each listing was compiled and run
with the version shown above,
and the output shown is the actual output produced.</p>

<h2 id="typed-claims-with-a-refinement">Typed Claims with a Refinement</h2>

<p>The blueprint requires every claim
to carry a confidence value that has a meaning.
A bare integer cannot enforce the range
a confidence must inhabit.
A <a href="https://en.wikipedia.org/wiki/Refinement_type">refinement type</a>
can, and the check is proved at construction.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">in_range</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="k">newtype</span><span class="w"> </span><span class="nc">Confidence</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">in_range</span><span class="p">;</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">raw</span><span class="p">(</span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="nc">Confidence</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">c</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">Word</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Confidence</span><span class="p">(</span><span class="mi">64</span><span class="p">);</span><span class="w">
    </span><span class="n">raw</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Running it produces the value.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 01_typed_claims.kel
64
</code></pre></div></div>

<p>The value of the refinement is what it refuses.
A confidence outside the range
is not a runtime error to be caught.
It is rejected before the program runs.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">raw</span><span class="p">(</span><span class="nc">Confidence</span><span class="p">(</span><span class="mi">150</span><span class="p">))</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 01b_typed_claims_reject.kel
error: compile: 5:9: refinement check <span class="sb">`</span>in_range<span class="sb">`</span> provably fails <span class="k">for </span>newtype <span class="sb">`</span>Confidence<span class="sb">`</span> at compile <span class="nb">time </span>on argument 150
</code></pre></div></div>

<p>The rejection is the feature.
A claim whose confidence is meaningless
cannot be constructed.</p>

<h2 id="terminal-state-routing">Terminal-State Routing</h2>

<p>The blueprint insists that the controller
has no terminal state that means
“produce a best guess anyway.”
Given a critic verdict and a calibrated confidence,
the controller routes to exactly one
of four terminal states.
The critic is not written here.
Its verdict is an input,
supplied by the host.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">in_range</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">bool</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&lt;=</span><span class="w"> </span><span class="mi">100</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="k">newtype</span><span class="w"> </span><span class="nc">Confidence</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="k">where</span><span class="w"> </span><span class="n">in_range</span><span class="p">;</span><span class="w">

</span><span class="k">enum</span><span class="w"> </span><span class="nc">Verdict</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nc">Refuted</span><span class="p">,</span><span class="w">
    </span><span class="nc">Unsupported</span><span class="p">,</span><span class="w">
    </span><span class="nc">IllPosed</span><span class="p">,</span><span class="w">
    </span><span class="nc">Supported</span><span class="p">,</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">answer_or_hedge</span><span class="p">(</span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="nc">Confidence</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">Word</span><span class="p">;</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">&gt;=</span><span class="w"> </span><span class="mi">80</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide</span><span class="p">(</span><span class="n">v</span><span class="p">:</span><span class="w"> </span><span class="nc">Verdict</span><span class="p">,</span><span class="w"> </span><span class="n">c</span><span class="p">:</span><span class="w"> </span><span class="nc">Confidence</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">match</span><span class="w"> </span><span class="n">v</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nc">Verdict</span><span class="o">::</span><span class="nc">Refuted</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nc">Verdict</span><span class="o">::</span><span class="nc">Unsupported</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
        </span><span class="nc">Verdict</span><span class="o">::</span><span class="nc">IllPosed</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
        </span><span class="nc">Verdict</span><span class="o">::</span><span class="nc">Supported</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">answer_or_hedge</span><span class="p">(</span><span class="n">c</span><span class="p">),</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">decide</span><span class="p">(</span><span class="nc">Verdict</span><span class="o">::</span><span class="nc">Supported</span><span class="p">,</span><span class="w"> </span><span class="nc">Confidence</span><span class="p">(</span><span class="mi">64</span><span class="p">))</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The decision codes are 0 for answer,
1 for answer with stated uncertainty,
2 for abstain, and 3 for request reframing.
A supported claim at a confidence of 64
falls below the threshold of 80,
so the controller answers with stated uncertainty.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 02_route.kel
1
</code></pre></div></div>

<p>A refuted or unsupported claim routes to abstention.
An ill-posed question routes to a request for reframing.
The match is total,
so every verdict has a defined terminal state,
and the verifier confirms it.</p>

<h2 id="the-fact-gate">The Fact Gate</h2>

<p>The governance layer must guarantee
that an ungrounded claim cannot reach the output
without passing an audited gate.
Keleusma expresses this with an
<a href="https://github.com/sgeos/keleusma/tree/master/docs/guide">information-flow label</a>.
A label rides on a value,
and the language refuses to let a labelled value
flow into a place that does not accept the label.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">commit</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">claim</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Unverified</span><span class="p">;</span><span class="w">
    </span><span class="n">commit</span><span class="p">(</span><span class="nd">declassify</span><span class="w"> </span><span class="n">claim</span><span class="nd">@Unverified</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The output boundary <code class="language-plaintext highlighter-rouge">commit</code> accepts only a plain <code class="language-plaintext highlighter-rouge">Word</code>.
The proposer’s claim arrives labelled <code class="language-plaintext highlighter-rouge">Unverified</code>.
The single line <code class="language-plaintext highlighter-rouge">declassify claim@Unverified</code>
is the one visible, greppable place
where a verified claim is released.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 03_fact_gate.kel
42
</code></pre></div></div>

<p>Remove the gate, and the leak is proved
before the program runs.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">claim</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="w"> </span><span class="mi">42</span><span class="nd">@Unverified</span><span class="p">;</span><span class="w">
    </span><span class="n">commit</span><span class="p">(</span><span class="n">claim</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 03b_fact_gate_leak.kel
error: compile: 17:12: <span class="nb">type </span>error: argument to <span class="sb">`</span>commit<span class="sb">`</span> expects Word, got Word@Unverified
</code></pre></div></div>

<p>A reviewer auditing the kernel
finds every release of an unverified claim
by searching for the word <code class="language-plaintext highlighter-rouge">declassify</code>.
There is no other way out.</p>

<h2 id="the-call-yield-resume-lifecycle">The Call-Yield-Resume Lifecycle</h2>

<p>The controller does not run to completion in one piece.
It pauses to let the host run the proposer and the critic,
then resumes with their result and decides.
That is a non-atomic total function,
written with the <code class="language-plaintext highlighter-rouge">yield</code> keyword.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w">        </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c1">// refuted     -&gt; abstain</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">        </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c1">// unsupported -&gt; abstain</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">        </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c1">// ill-posed   -&gt; request reframing</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w">  </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">  </span><span class="c1">// supported   -&gt; answer</span><span class="w">

</span><span class="kd">yield</span><span class="w"> </span><span class="n">main</span><span class="p">(</span><span class="n">tick</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">verdict</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">yield</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w">
    </span><span class="n">decide_code</span><span class="p">(</span><span class="n">verdict</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The runner drives this lifecycle
through a tick-counter convention
that arrived in the 0.2.1 release.
It calls the entry with tick 1.
The script yields a Word,
the host computes the next tick
as the yielded value plus one,
and resumes the script with that value.
A <code class="language-plaintext highlighter-rouge">yield</code> entry point ends
when control returns from the function,
and the runner prints the final value.
Here the script yields 1,
the host resumes with 2,
and the controller routes verdict code 2,
an ill-posed question,
to terminal state 3, request reframing.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run 04_controller_tick.kel
Int<span class="o">(</span>3<span class="o">)</span>
</code></pre></div></div>

<p>In a real host the resume value
is the critic’s verdict rather than a tick counter,
and the embedding host drives the same lifecycle
through its <a href="https://github.com/sgeos/keleusma">runtime interface</a>.</p>

<p>The long-running form is a productive divergent function
that yields a decision on every cycle and never finishes,
the steady beat of a governed agent
that runs indefinitely.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w">       </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">       </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="w">       </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">loop</span><span class="w"> </span><span class="n">main</span><span class="p">(</span><span class="n">verdict</span><span class="p">:</span><span class="w"> </span><span class="kt">Word</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">Word</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">decision</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">decide_code</span><span class="p">(</span><span class="n">verdict</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">_next</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">yield</span><span class="w"> </span><span class="n">decision</span><span class="p">;</span><span class="w">
    </span><span class="n">decision</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The runner drives this form continuously,
rate-limited by the <code class="language-plaintext highlighter-rouge">--tick-interval</code> flag,
and it stops only when the script calls <code class="language-plaintext highlighter-rouge">shell::exit</code>
or the operator interrupts it.
The stock runner does not print the per-cycle decisions,
so a host that consumes them is needed for visible output,
but the verifier still proves the loop
productive and bounded per cycle before it runs.</p>

<p>On the 0.2.0 release the resume driver is absent.
There the <code class="language-plaintext highlighter-rouge">yield</code> and <code class="language-plaintext highlighter-rouge">loop</code> entry points
still lex, type-check, and pass the verifier,
which <code class="language-plaintext highlighter-rouge">keleusma compile</code> confirms by writing the bytecode,
but the stock 0.2.0 runner cannot drive them to completion.</p>

<h2 id="what-this-does-and-does-not-show">What This Does and Does Not Show</h2>

<p>The honest accounting matters
more in this subject than in most.</p>

<p>What the kernel shows is that the deterministic spine
of the blueprint can be written
in a language that proves termination
and bounded resources before execution,
that typed claims and their confidence ranges
can be enforced at construction,
that the controller’s terminal states are total
with no fall-through to a guess,
and that an unverified claim cannot reach the output
except through a single audited release point.
Keleusma version 0.2.0 also adds
<a href="https://github.com/sgeos/keleusma">cryptographic module signing</a>,
so the kernel itself can be delivered
as a tamper-evident, origin-authentic artifact,
which is the property the blueprint wants
for any policy or controller module
distributed to a node.</p>

<p>What the kernel does not show is a truthful machine.
The proposer, the critics, the retrieval grounding,
the calibrator, and the formal prover
are absent by necessity.
They are tensor-compute and nondeterministic search,
and Keleusma deliberately keeps that work
on the far side of its external-native boundary.
The information-flow guarantee, moreover,
is a compile-time property of the score,
not of the orchestra.
It disciplines what the kernel expresses,
not what the host does once a value is released.
The refinement checker is conservative as well.
It proves decidable predicates,
not the rich semantic policies
a full verifier would need.</p>

<p>These limits are not failures of the demonstration.
They are the boundary the blueprint itself draws,
made concrete.
The kernel is the part that can be made rigid and auditable,
and it is exactly the part shown here.
The rest remains, correctly, outside it.</p>

<h2 id="conclusion">Conclusion</h2>

<p>The truthful-machine blueprint is a hypothesis,
and this article does not change that.
What it establishes is narrower and verifiable.
The control-and-governance kernel of that design
is expressible today in a language
that proves the kernel total and bounded
before it runs,
that enforces typed claims and an audited fact gate
at compile time,
and that can ship the kernel as a signed artifact.
The neural and symbolic-prover components
remain unbuilt and, in Keleusma, unbuildable,
which is precisely where the blueprint
says they should live.
A small, exact, trustworthy core
inside a large and untrusted host
is the shape of the kernel
and the shape of the language alike.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://crates.io/crates/keleusma">Keleusma, Crate on crates.io</a></li>
  <li><a href="https://github.com/sgeos/keleusma">Keleusma, GitHub Repository</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/master/docs/guide">Keleusma, Language Guide</a></li>
  <li><a href="https://en.wikipedia.org/wiki/EdDSA">Reference, EdDSA and Ed25519</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Refinement_type">Reference, Refinement Type</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Total_functional_programming">Reference, Total Functional Programming</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Worst-case_execution_time">Reference, Worst-Case Execution Time</a></li>
  <li><a href="/ai/philosophy/2026/05/26/neurosymbolic_blueprint_for_truthful_machines.html">Related Post, A Speculative Neurosymbolic Blueprint for Truthful Machines</a></li>
  <li><a href="/rust/embedded/programming/2026/03/14/keleusma_getting_started.html">Related Post, Getting Started with Keleusma 0.1.1</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="ai" /><category term="rust" /><category term="programming" /></entry><entry><title type="html">Getting Started with Keleusma 0.1.1</title><link href="https://sgeos.github.io/rust/embedded/programming/2026/03/14/keleusma_getting_started.html" rel="alternate" type="text/html" title="Getting Started with Keleusma 0.1.1" /><published>2026-03-14T10:31:00+00:00</published><updated>2026-03-14T10:31:00+00:00</updated><id>https://sgeos.github.io/rust/embedded/programming/2026/03/14/keleusma_getting_started</id><content type="html" xml:base="https://sgeos.github.io/rust/embedded/programming/2026/03/14/keleusma_getting_started.html"><![CDATA[<!-- A107 -->
<script>console.log("A107");</script>

<p><a href="https://github.com/sgeos/keleusma">Keleusma</a> is a Total Functional Stream Processor
that compiles to bytecode and runs on a stack-based virtual machine.
It targets <code class="language-plaintext highlighter-rouge">no_std + alloc</code> embedded environments
and provides definitive Worst-Case Execution Time
and Worst-Case Memory Usage guarantees through static verification.
The language enforces five guarantees at compile time.
Every function terminates.
Every stream produces output on every tick.
Every execution path has a bounded step count.
Every execution path has a bounded memory footprint.
Running code can be replaced at designated boundaries
without stopping the virtual machine.</p>

<p>The name derives from the Greek word <em>keleusma</em>,
meaning a command or rhythmic signal.
Ancient rowing masters used the keleusma
to synchronize oarsmen on warships.
The language applies the same principle to computation.
Scripts execute in bounded, predictable cycles
synchronized to a host-driven clock.</p>

<p>Keleusma targets applications
where deterministic execution is a hard requirement.
Audio engines must produce samples every tick
without buffer underruns.
Game scripting systems must complete logic updates
within a fixed frame budget.
Safety-critical embedded systems must guarantee
worst-case timing for certification.
The language addresses these domains
by making unbounded computation inexpressible
rather than merely discouraged.</p>

<p>This article provides a practical introduction
to the Keleusma language and toolchain.
It covers installation, the type system,
the three function kinds that encode the guarantee taxonomy,
pattern matching, the pipeline operator,
built-in functions, the interactive read-evaluate-print loop,
embedding the virtual machine in a Rust host application,
hot code swapping, worst-case analysis,
and bytecode compilation.
Readers familiar with
<a href="/rust/no_std/embedded/2026/01/16/no_std_rust_getting_started.html">no_std Rust programming</a>
will find the embedding interface straightforward.</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-03-14 10:31:00 +0000

<span class="c"># Keleusma Version</span>
<span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.1.1

<span class="c"># Rust Version</span>
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.88.0 <span class="o">(</span>2025-06-25<span class="o">)</span>

<span class="nv">$ </span>rustc <span class="nt">--version</span>
rustc 1.88.0 <span class="o">(</span>2025-06-25<span class="o">)</span>
</code></pre></div></div>

<h2 id="installation">Installation</h2>

<p>Keleusma is available on <a href="https://crates.io/crates/keleusma">crates.io</a>
and as source on <a href="https://github.com/sgeos/keleusma">GitHub</a>.
The command-line interface is distributed
as a separate workspace crate called <code class="language-plaintext highlighter-rouge">keleusma-cli</code>.</p>

<h3 id="install-from-source">Install from Source</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/sgeos/keleusma
<span class="nb">cd </span>keleusma
cargo <span class="nb">install</span> <span class="nt">--path</span> keleusma-cli <span class="nt">--bin</span> keleusma
</code></pre></div></div>

<h3 id="verify-installation">Verify Installation</h3>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma <span class="nt">--version</span>
keleusma 0.1.1

<span class="nv">$ </span>keleusma <span class="nt">--help</span>
keleusma: command-line frontend <span class="k">for </span>the Keleusma scripting language

Usage:
  keleusma &lt;subcommand&gt; <span class="o">[</span>options]
  keleusma &lt;file&gt;.kel               <span class="o">(</span>shorthand <span class="k">for</span> <span class="sb">`</span>run<span class="sb">`</span><span class="o">)</span>

Subcommands:
  run &lt;file&gt;                        Compile and execute a script
  compile &lt;file&gt; <span class="o">[</span><span class="nt">-o</span> &lt;output&gt;]      Compile to bytecode
  repl                              Start interactive REPL
  <span class="nb">help</span>, <span class="nt">--help</span>, <span class="nt">-h</span>                  Show this <span class="nb">help
  </span>version, <span class="nt">--version</span>, <span class="nt">-V</span>            Show version

Examples:
  keleusma run hello.kel
  keleusma hello.kel
  keleusma compile hello.kel <span class="nt">-o</span> hello.kel.bin
  keleusma repl
</code></pre></div></div>

<h2 id="first-program">First Program</h2>

<p>Create a file called <code class="language-plaintext highlighter-rouge">hello.kel</code> with the following contents.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">square</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">4</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">sum_of_squares</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">square</span><span class="p">(</span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">square</span><span class="p">(</span><span class="n">b</span><span class="p">);</span><span class="w">
    </span><span class="n">sum_of_squares</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Run the program.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run hello.kel
24
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">main</code> function is the entry point.
It computes <code class="language-plaintext highlighter-rouge">3^2 + 4^2 - 1 = 9 + 16 - 1 = 24</code>.
The program compiles to bytecode,
passes through the structural verifier,
and executes on the stack-based virtual machine.
The verifier confirms that every execution path terminates
and that memory usage is bounded
before the virtual machine begins execution.</p>

<h2 id="language-basics">Language Basics</h2>

<h3 id="primitive-types">Primitive Types</h3>

<p>Keleusma provides five primitive types.</p>

<table>
  <thead>
    <tr>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">i64</code></td>
      <td>64-bit signed integer</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">f64</code></td>
      <td>64-bit floating-point number</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">bool</code></td>
      <td>Boolean value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">String</code></td>
      <td>UTF-8 string</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">()</code></td>
      <td>Unit type</td>
    </tr>
  </tbody>
</table>

<h3 id="variables">Variables</h3>

<p>All local bindings are immutable.
Rebinding a name shadows the previous binding
without mutating the original value.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span><span class="w"> </span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">42</span><span class="p">;</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">3.14</span><span class="p">;</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</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></code></pre></div></div>

<p>Type annotations are optional
when the type can be inferred from context.</p>

<h3 id="operators">Operators</h3>

<p>Keleusma supports standard arithmetic, comparison, and logical operators.</p>

<p><strong>Arithmetic:</strong> <code class="language-plaintext highlighter-rouge">+</code>, <code class="language-plaintext highlighter-rouge">-</code>, <code class="language-plaintext highlighter-rouge">*</code>, <code class="language-plaintext highlighter-rouge">/</code>, <code class="language-plaintext highlighter-rouge">%</code></p>

<p><strong>Comparison:</strong> <code class="language-plaintext highlighter-rouge">==</code>, <code class="language-plaintext highlighter-rouge">!=</code>, <code class="language-plaintext highlighter-rouge">&lt;</code>, <code class="language-plaintext highlighter-rouge">&gt;</code>, <code class="language-plaintext highlighter-rouge">&lt;=</code>, <code class="language-plaintext highlighter-rouge">&gt;=</code></p>

<p><strong>Logical:</strong> <code class="language-plaintext highlighter-rouge">and</code>, <code class="language-plaintext highlighter-rouge">or</code>, <code class="language-plaintext highlighter-rouge">not</code></p>

<p><strong>Type cast:</strong> <code class="language-plaintext highlighter-rouge">as</code></p>

<p><strong>Pipeline:</strong> <code class="language-plaintext highlighter-rouge">|&gt;</code></p>

<p>Logical operators use short-circuit evaluation.
The <code class="language-plaintext highlighter-rouge">as</code> operator performs explicit type conversion
between numeric types.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">42</span><span class="p">;</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">f</span><span class="p">:</span><span class="w"> </span><span class="kt">f64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">f64</span><span class="p">;</span><span class="w">
</span></code></pre></div></div>

<h3 id="conditionals">Conditionals</h3>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">abs_value</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Conditionals are expressions and evaluate to a value.
Both branches must produce the same type.</p>

<h3 id="bounded-iteration">Bounded Iteration</h3>

<p>The <code class="language-plaintext highlighter-rouge">for</code> loop iterates over ranges or arrays.
All <code class="language-plaintext highlighter-rouge">for</code> loops are bounded by construction
because ranges have a known size
and arrays have a known length.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">xs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">];</span><span class="w">

    </span><span class="k">for</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">xs</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="k">let</span><span class="w"> </span><span class="n">_doubled</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">4</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="k">let</span><span class="w"> </span><span class="n">_squared</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">i</span><span class="p">;</span><span class="w">
    </span><span class="p">}</span><span class="w">

    </span><span class="n">xs</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The range <code class="language-plaintext highlighter-rouge">0..4</code> produces values 0, 1, 2, and 3.
The upper bound is exclusive.
The <code class="language-plaintext highlighter-rouge">break</code> keyword exits a <code class="language-plaintext highlighter-rouge">for</code> loop early.</p>

<h2 id="three-function-kinds">Three Function Kinds</h2>

<p>The central design of Keleusma organizes all functions
into three categories
that collectively guarantee bounded execution.
Each category has a dedicated keyword
and a distinct set of obligations.</p>

<h3 id="atomic-total-functions">Atomic Total Functions</h3>

<p>The <code class="language-plaintext highlighter-rouge">fn</code> keyword declares an atomic total function.
An atomic total function must terminate on every input.
It may not yield, may not recurse,
and must return a value.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">clamp</span><span class="p">(</span><span class="n">val</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="n">lo</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="n">hi</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">if</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="n">lo</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">lo</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="k">else</span><span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="n">hi</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">hi</span><span class="w"> </span><span class="p">}</span><span class="w">
    </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">val</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The verifier confirms termination statically.
Unbounded recursion and infinite loops
are syntactically impossible in <code class="language-plaintext highlighter-rouge">fn</code> bodies.</p>

<h3 id="non-atomic-total-functions">Non-Atomic Total Functions</h3>

<p>The <code class="language-plaintext highlighter-rouge">yield</code> keyword declares a non-atomic total function.
A non-atomic total function may yield control
back to the host one or more times
but must eventually return a final value.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">yield</span><span class="w"> </span><span class="n">prompt</span><span class="p">(</span><span class="n">question</span><span class="p">:</span><span class="w"> </span><span class="kt">String</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">answer</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">yield</span><span class="w"> </span><span class="n">question</span><span class="p">;</span><span class="w">
    </span><span class="n">answer</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>When the function executes <code class="language-plaintext highlighter-rouge">yield question</code>,
it suspends and sends <code class="language-plaintext highlighter-rouge">question</code> to the host.
The host provides a response,
which becomes the value of the <code class="language-plaintext highlighter-rouge">yield</code> expression.
The function must eventually reach a <code class="language-plaintext highlighter-rouge">return</code> point.</p>

<h3 id="productive-divergent-functions">Productive Divergent Functions</h3>

<p>The <code class="language-plaintext highlighter-rouge">loop</code> keyword declares a productive divergent function.
A productive divergent function never returns.
It runs indefinitely, processing a stream of inputs
and producing a stream of outputs.
It must yield on every iteration.
Only one <code class="language-plaintext highlighter-rouge">loop</code> function may exist per script.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">loop</span><span class="w"> </span><span class="n">main</span><span class="p">(</span><span class="n">input</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">input</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">input</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kd">yield</span><span class="w"> </span><span class="n">result</span><span class="p">;</span><span class="w">
    </span><span class="n">input</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The verifier confirms that every control path
from the stream entry point to the reset boundary
contains at least one <code class="language-plaintext highlighter-rouge">yield</code>.
This guarantees <a href="https://en.wikipedia.org/wiki/Productivity_(computer_science)">productivity</a>.
The host drives execution by providing inputs
and consuming outputs at each yield point.</p>

<h3 id="why-three-kinds">Why Three Kinds</h3>

<p>The three-kind taxonomy ensures
that the verifier can assign a finite cost
to every execution path.
Atomic total functions have a fixed step count.
Non-atomic total functions have a fixed step count per segment.
Productive divergent functions have a fixed step count per tick.
Together, these properties enable
static computation of worst-case execution time
for any single tick of the system.</p>

<h2 id="composite-types">Composite Types</h2>

<h3 id="structs">Structs</h3>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span><span class="w"> </span><span class="nc">Point</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">manhattan_norm</span><span class="p">(</span><span class="n">p</span><span class="p">:</span><span class="w"> </span><span class="nc">Point</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">p</span><span class="p">.</span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">p</span><span class="p">.</span><span class="n">y</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">origin_offset</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Point</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">:</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="p">};</span><span class="w">
    </span><span class="n">manhattan_norm</span><span class="p">(</span><span class="n">origin_offset</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Structs group named fields into a single value.
Field access uses dot notation.</p>

<h3 id="enums">Enums</h3>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">enum</span><span class="w"> </span><span class="nc">Shape</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="nc">Circle</span><span class="p">(</span><span class="kt">i64</span><span class="p">),</span><span class="w">
    </span><span class="nc">Rectangle</span><span class="p">(</span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="kt">i64</span><span class="p">),</span><span class="w">
    </span><span class="nc">Square</span><span class="p">(</span><span class="kt">i64</span><span class="p">),</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">area_estimate</span><span class="p">(</span><span class="n">s</span><span class="p">:</span><span class="w"> </span><span class="nc">Shape</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">match</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="p">{</span><span class="w">
        </span><span class="nc">Shape</span><span class="o">::</span><span class="nc">Circle</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">r</span><span class="p">,</span><span class="w">
        </span><span class="nc">Shape</span><span class="o">::</span><span class="nc">Rectangle</span><span class="p">(</span><span class="n">w</span><span class="p">,</span><span class="w"> </span><span class="n">h</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">w</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">h</span><span class="p">,</span><span class="w">
        </span><span class="nc">Shape</span><span class="o">::</span><span class="nc">Square</span><span class="p">(</span><span class="n">side</span><span class="p">)</span><span class="w"> </span><span class="o">=&gt;</span><span class="w"> </span><span class="n">side</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">side</span><span class="p">,</span><span class="w">
    </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">shape</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nc">Shape</span><span class="o">::</span><span class="nc">Rectangle</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">);</span><span class="w">
    </span><span class="n">area_estimate</span><span class="p">(</span><span class="n">shape</span><span class="p">)</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Enums define a closed set of variants.
Each variant may carry associated data.
The <code class="language-plaintext highlighter-rouge">match</code> expression destructures enum values.
Match expressions must be exhaustive
or include a wildcard <code class="language-plaintext highlighter-rouge">_</code> arm.</p>

<h3 id="tuples-and-arrays">Tuples and Arrays</h3>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span><span class="w"> </span><span class="n">pair</span><span class="p">:</span><span class="w"> </span><span class="p">(</span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">xs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="p">];</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">repeated</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="p">;</span><span class="w"> </span><span class="mi">8</span><span class="p">];</span><span class="w">
</span></code></pre></div></div>

<p>Tuples group heterogeneous values by position.
Arrays hold homogeneous values of fixed length.
The <code class="language-plaintext highlighter-rouge">[value; count]</code> syntax creates an array
filled with <code class="language-plaintext highlighter-rouge">count</code> copies of <code class="language-plaintext highlighter-rouge">value</code>.</p>

<h3 id="option-type">Option Type</h3>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span><span class="w"> </span><span class="n">some_value</span><span class="p">:</span><span class="w"> </span><span class="kt">Option</span><span class="o">&lt;</span><span class="kt">i64</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">Option</span><span class="o">::</span><span class="nc">Some</span><span class="p">(</span><span class="mi">42</span><span class="p">);</span><span class="w">
</span><span class="k">let</span><span class="w"> </span><span class="n">no_value</span><span class="p">:</span><span class="w"> </span><span class="kt">Option</span><span class="o">&lt;</span><span class="kt">i64</span><span class="o">&gt;</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kt">Option</span><span class="o">::</span><span class="nc">None</span><span class="p">;</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">Option</code> type represents a value that may or may not exist.</p>

<h2 id="pipeline-operator">Pipeline Operator</h2>

<p>The pipeline operator <code class="language-plaintext highlighter-rouge">|&gt;</code> threads the result
of the left expression
as the first argument to the right function call.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="n">y</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">square</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="mi">6</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">double</span><span class="p">()</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">square</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This program produces 169.
The value 6 flows through <code class="language-plaintext highlighter-rouge">double</code> to produce 12,
then through <code class="language-plaintext highlighter-rouge">add(_, 1)</code> to produce 13,
then through <code class="language-plaintext highlighter-rouge">square</code> to produce 169.</p>

<p>For functions where the piped value
should not be the first argument,
use the underscore placeholder.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">value</span><span class="w"> </span><span class="o">|&gt;</span><span class="w"> </span><span class="n">insert</span><span class="p">(</span><span class="n">collection</span><span class="p">,</span><span class="w"> </span><span class="n">_</span><span class="p">)</span><span class="w">
</span></code></pre></div></div>

<p>The underscore marks
where the piped value should be inserted.</p>

<h2 id="multiheaded-functions-and-guards">Multiheaded Functions and Guards</h2>

<p>A single function name may have multiple heads,
each with its own parameter pattern.
The runtime tries the heads in source order
and dispatches to the first one whose pattern matches.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span><span class="w">            </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="w">            </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w">       </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">11</span><span class="p">);</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">d</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nd">classify</span><span class="p">(</span><span class="mi">7</span><span class="p">);</span><span class="w">
    </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">d</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">28</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This program produces 11.
The first head matches only the literal 0.
The second head matches only the literal 1.
The third head matches any other integer.</p>

<p>Guard clauses refine pattern matching
with additional conditions.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">sign</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="k">when</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"positive"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">sign</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="k">when</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"negative"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="kd">fn</span><span class="w"> </span><span class="n">sign</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="s2">"zero"</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">when</code> clause is evaluated
after the parameter pattern matches.
If the guard fails, dispatch continues
to the next head.</p>

<h2 id="generics-and-traits">Generics and Traits</h2>

<p>Keleusma supports generic type parameters
and trait-based polymorphism.
Generics are monomorphized at compile time.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">id</span><span class="o">&lt;</span><span class="nc">T</span><span class="o">&gt;</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="nc">T</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="nc">T</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>Traits declare a set of function signatures
that a type must implement.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">trait</span><span class="w"> </span><span class="nc">Doubler</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="p">;</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="k">impl</span><span class="w"> </span><span class="nc">Doubler</span><span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="kd">fn</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">n</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">42</span><span class="p">;</span><span class="w">
    </span><span class="n">n</span><span class="p">.</span><span class="n">double</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<p>This program produces 84.
Method dispatch uses dot notation.
The compiler resolves the concrete implementation
at compile time through monomorphization.</p>

<p>Trait bounds constrain type parameters.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">fn</span><span class="w"> </span><span class="n">use_doubler</span><span class="o">&lt;</span><span class="nc">T</span><span class="p">:</span><span class="w"> </span><span class="nc">Doubler</span><span class="o">&gt;</span><span class="p">(</span><span class="n">x</span><span class="p">:</span><span class="w"> </span><span class="nc">T</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">x</span><span class="p">.</span><span class="n">double</span><span class="p">()</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<h2 id="strings-and-interpolation">Strings and Interpolation</h2>

<p>Keleusma distinguishes two string categories.
Static strings are stored in the read-only data segment
and can flow anywhere in the program.
Dynamic strings are arena-allocated
and cannot cross yield boundaries.</p>

<p>String literals produce static strings.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span><span class="w"> </span><span class="n">msg</span><span class="p">:</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"hello"</span><span class="p">;</span><span class="w">
</span></code></pre></div></div>

<p>F-string interpolation provides formatted string construction.
The <code class="language-plaintext highlighter-rouge">f"..."</code> syntax desugars at lex time
into chains of <code class="language-plaintext highlighter-rouge">concat</code> and <code class="language-plaintext highlighter-rouge">to_string</code> calls.
Scripts that use f-strings must declare
their dependency on these native functions.</p>

<div class="language-keleusma highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span><span class="w"> </span><span class="n">to_string</span><span class="w">
</span><span class="k">use</span><span class="w"> </span><span class="n">concat</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="p">)</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="w">
</span><span class="p">}</span><span class="w">

</span><span class="kd">fn</span><span class="w"> </span><span class="n">main</span><span class="p">()</span><span class="w"> </span><span class="o">-&gt;</span><span class="w"> </span><span class="kt">String</span><span class="w"> </span><span class="p">{</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">name</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">"Keleusma"</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">a</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">7</span><span class="p">;</span><span class="w">
    </span><span class="k">let</span><span class="w"> </span><span class="n">b</span><span class="p">:</span><span class="w"> </span><span class="kt">i64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w">
    </span><span class="n">f</span><span class="s2">"hello, {name}! {a} plus {b} is {add(a, b)}"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma run fstring.kel
hello, Keleusma! 7 plus 2 is 9
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">use</code> declarations inform the compiler
that these native functions are provided by the host.</p>

<h2 id="built-in-functions">Built-in Functions</h2>

<p>The CLI runner registers two sets
of native functions automatically.</p>

<h3 id="utility-functions">Utility Functions</h3>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Signature</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">to_string</code></td>
      <td><code class="language-plaintext highlighter-rouge">(i64 or f64) -&gt; String</code></td>
      <td>Convert number to string</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">length</code></td>
      <td><code class="language-plaintext highlighter-rouge">(String or Array) -&gt; i64</code></td>
      <td>Get length</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">concat</code></td>
      <td><code class="language-plaintext highlighter-rouge">(String, String) -&gt; String</code></td>
      <td>Concatenate strings</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">slice</code></td>
      <td><code class="language-plaintext highlighter-rouge">(String, i64, i64) -&gt; String</code></td>
      <td>Extract substring</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">println</code></td>
      <td><code class="language-plaintext highlighter-rouge">(String) -&gt; ()</code></td>
      <td>Print to standard output</td>
    </tr>
  </tbody>
</table>

<h3 id="math-functions">Math Functions</h3>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Signature</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::sin</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; f64</code></td>
      <td>Sine</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::cos</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; f64</code></td>
      <td>Cosine</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::pow</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64, f64) -&gt; f64</code></td>
      <td>Exponentiation</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::abs</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; f64</code></td>
      <td>Absolute value</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::min</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64, f64) -&gt; f64</code></td>
      <td>Minimum</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::max</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64, f64) -&gt; f64</code></td>
      <td>Maximum</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::clamp</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64, f64, f64) -&gt; f64</code></td>
      <td>Clamp to range</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">math::lerp</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64, f64, f64) -&gt; f64</code></td>
      <td>Linear interpolation</td>
    </tr>
  </tbody>
</table>

<h3 id="audio-functions">Audio Functions</h3>

<table>
  <thead>
    <tr>
      <th>Function</th>
      <th>Signature</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">audio::midi_to_freq</code></td>
      <td><code class="language-plaintext highlighter-rouge">(i64) -&gt; f64</code></td>
      <td>MIDI note to frequency in Hz</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">audio::freq_to_midi</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; i64</code></td>
      <td>Frequency to nearest MIDI note</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">audio::db_to_linear</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; f64</code></td>
      <td>Decibels to linear amplitude</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">audio::linear_to_db</code></td>
      <td><code class="language-plaintext highlighter-rouge">(f64) -&gt; f64</code></td>
      <td>Linear amplitude to decibels</td>
    </tr>
  </tbody>
</table>

<p>All math and audio functions accept both <code class="language-plaintext highlighter-rouge">i64</code> and <code class="language-plaintext highlighter-rouge">f64</code> arguments.
Integer arguments are widened to floating-point automatically.</p>

<h2 id="the-repl">The REPL</h2>

<p>The interactive read-evaluate-print loop
allows experimentation without creating script files.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma repl
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&gt; 1 + 2
3
&gt; fn double(x: i64) -&gt; i64 { x + x }
defined: double
&gt; double(21)
42
&gt; :help
:help    Show this help
:quit    Exit the REPL
:reset   Clear all definitions
:show    Show defined functions
&gt; :quit
</code></pre></div></div>

<p>The REPL supports function definitions, expressions,
and colon-prefixed meta-commands.
Definitions persist across evaluations
until <code class="language-plaintext highlighter-rouge">:reset</code> is issued.</p>

<h2 id="embedding-in-rust">Embedding in Rust</h2>

<p>Keleusma is designed to be embedded in Rust host applications.
The library crate provides the full compilation pipeline
and virtual machine as a library API.</p>

<h3 id="minimal-example">Minimal Example</h3>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">compiler</span><span class="p">::</span><span class="n">compile</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">lexer</span><span class="p">::</span><span class="n">tokenize</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">parser</span><span class="p">::</span><span class="n">parse</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">vm</span><span class="p">::{</span><span class="n">DEFAULT_ARENA_CAPACITY</span><span class="p">,</span> <span class="n">Vm</span><span class="p">,</span> <span class="n">VmState</span><span class="p">};</span>
<span class="k">use</span> <span class="nn">keleusma</span><span class="p">::{</span><span class="n">Arena</span><span class="p">,</span> <span class="n">Value</span><span class="p">};</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">source</span> <span class="o">=</span> <span class="s">r#"
        fn double(x: i64) -&gt; i64 { x * x }
        fn main(n: i64) -&gt; i64 { n |&gt; double() }
    "#</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">tokens</span> <span class="o">=</span> <span class="nf">tokenize</span><span class="p">(</span><span class="n">source</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"lex"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">program</span> <span class="o">=</span> <span class="nf">parse</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tokens</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"parse"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">module</span> <span class="o">=</span> <span class="nf">compile</span><span class="p">(</span><span class="o">&amp;</span><span class="n">program</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"compile"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">arena</span> <span class="o">=</span> <span class="nn">Arena</span><span class="p">::</span><span class="nf">with_capacity</span><span class="p">(</span><span class="n">DEFAULT_ARENA_CAPACITY</span><span class="p">);</span>
    <span class="k">let</span> <span class="k">mut</span> <span class="n">vm</span> <span class="o">=</span> <span class="nn">Vm</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">module</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">arena</span><span class="p">)</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"verify"</span><span class="p">);</span>

    <span class="k">match</span> <span class="n">vm</span><span class="nf">.call</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="nn">Value</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="mi">21</span><span class="p">)])</span><span class="nf">.unwrap</span><span class="p">()</span> <span class="p">{</span>
        <span class="nn">VmState</span><span class="p">::</span><span class="nf">Finished</span><span class="p">(</span><span class="nn">Value</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="n">n</span><span class="p">))</span> <span class="k">=&gt;</span> <span class="nd">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">n</span><span class="p">),</span>
        <span class="n">_</span> <span class="k">=&gt;</span> <span class="nd">unreachable!</span><span class="p">(),</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The host owns the <a href="https://en.wikipedia.org/wiki/Region-based_memory_management">arena allocator</a>.
The virtual machine borrows the arena
for its operand stack, call frames, and dynamic string storage.
The default arena capacity is 64 kilobytes.</p>

<h3 id="registering-native-functions">Registering Native Functions</h3>

<p>Native functions allow scripts to call into host code.
The ergonomic typed interface is the recommended approach.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vm</span><span class="nf">.register_fn</span><span class="p">(</span><span class="s">"add"</span><span class="p">,</span> <span class="p">|</span><span class="n">a</span><span class="p">:</span> <span class="nb">i64</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">i64</span><span class="p">|</span> <span class="k">-&gt;</span> <span class="nb">i64</span> <span class="p">{</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="p">});</span>
<span class="n">vm</span><span class="nf">.register_fn</span><span class="p">(</span><span class="s">"sin"</span><span class="p">,</span> <span class="p">|</span><span class="n">x</span><span class="p">:</span> <span class="nb">f64</span><span class="p">|</span> <span class="k">-&gt;</span> <span class="nb">f64</span> <span class="p">{</span> <span class="nn">libm</span><span class="p">::</span><span class="nf">sin</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="p">});</span>
</code></pre></div></div>

<p>For functions that may fail, use the fallible interface.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">vm</span><span class="nf">.register_fn_fallible</span><span class="p">(</span><span class="s">"get_setting"</span><span class="p">,</span> <span class="p">|</span><span class="n">key</span><span class="p">:</span> <span class="nb">String</span><span class="p">|</span> <span class="k">-&gt;</span> <span class="nb">Result</span><span class="o">&lt;</span><span class="nb">String</span><span class="p">,</span> <span class="n">VmError</span><span class="o">&gt;</span> <span class="p">{</span>
    <span class="nf">fetch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">key</span><span class="p">)</span><span class="nf">.map_err</span><span class="p">(|</span><span class="n">e</span><span class="p">|</span> <span class="nn">VmError</span><span class="p">::</span><span class="nf">NativeError</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">e</span><span class="p">)))</span>
<span class="p">});</span>
</code></pre></div></div>

<p>Custom struct types can cross the host-guest boundary
using the <code class="language-plaintext highlighter-rouge">KeleusmaType</code> derive macro.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">keleusma</span><span class="p">::</span><span class="n">KeleusmaType</span><span class="p">;</span>

<span class="nd">#[derive(KeleusmaType,</span> <span class="nd">Debug,</span> <span class="nd">Clone)]</span>
<span class="k">struct</span> <span class="n">Point</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="nb">f64</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="nb">f64</span> <span class="p">}</span>

<span class="n">vm</span><span class="nf">.register_fn</span><span class="p">(</span><span class="s">"midpoint"</span><span class="p">,</span> <span class="p">|</span><span class="n">a</span><span class="p">:</span> <span class="n">Point</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="n">Point</span><span class="p">|</span> <span class="k">-&gt;</span> <span class="n">Point</span> <span class="p">{</span>
    <span class="n">Point</span> <span class="p">{</span> <span class="n">x</span><span class="p">:</span> <span class="p">(</span><span class="n">a</span><span class="py">.x</span> <span class="o">+</span> <span class="n">b</span><span class="py">.x</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span><span class="p">,</span> <span class="n">y</span><span class="p">:</span> <span class="p">(</span><span class="n">a</span><span class="py">.y</span> <span class="o">+</span> <span class="n">b</span><span class="py">.y</span><span class="p">)</span> <span class="o">/</span> <span class="mf">2.0</span> <span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>

<h3 id="vm-lifecycle">VM Lifecycle</h3>

<p>The virtual machine transitions between states
as it executes scripts.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">state</span> <span class="o">=</span> <span class="n">vm</span><span class="nf">.call</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="nn">Value</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="n">input</span><span class="p">)])</span><span class="o">?</span><span class="p">;</span>

<span class="k">loop</span> <span class="p">{</span>
    <span class="k">match</span> <span class="n">state</span> <span class="p">{</span>
        <span class="nn">VmState</span><span class="p">::</span><span class="nf">Yielded</span><span class="p">(</span><span class="n">output</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">reply</span> <span class="o">=</span> <span class="nf">host_process</span><span class="p">(</span><span class="n">output</span><span class="p">);</span>
            <span class="n">state</span> <span class="o">=</span> <span class="n">vm</span><span class="nf">.resume</span><span class="p">(</span><span class="n">reply</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="nn">VmState</span><span class="p">::</span><span class="n">Reset</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="c1">// Hot swap opportunity</span>
            <span class="n">state</span> <span class="o">=</span> <span class="n">vm</span><span class="nf">.resume</span><span class="p">(</span><span class="n">next_input</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="nn">VmState</span><span class="p">::</span><span class="nf">Finished</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="k">=&gt;</span> <span class="p">{</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>A <code class="language-plaintext highlighter-rouge">Yielded</code> state indicates
that the script has produced output
and awaits the next input.
A <code class="language-plaintext highlighter-rouge">Reset</code> state indicates a phase boundary
where hot code swapping is permitted.
A <code class="language-plaintext highlighter-rouge">Finished</code> state indicates
that the script has returned a final value.</p>

<h2 id="hot-code-swapping">Hot Code Swapping</h2>

<p>Keleusma supports replacing a running script
at designated reset boundaries
without stopping the virtual machine.
The data segment persists across the swap.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">match</span> <span class="n">vm</span><span class="nf">.resume</span><span class="p">(</span><span class="n">input</span><span class="p">)</span><span class="o">?</span> <span class="p">{</span>
    <span class="nn">VmState</span><span class="p">::</span><span class="n">Reset</span> <span class="k">=&gt;</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">new_module</span> <span class="o">=</span> <span class="nf">recompile_or_load</span><span class="p">()</span><span class="o">?</span><span class="p">;</span>
        <span class="k">let</span> <span class="n">initial_data</span> <span class="o">=</span> <span class="nd">vec!</span><span class="p">[</span><span class="nn">Value</span><span class="p">::</span><span class="nf">Int</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="n">slot_count</span><span class="p">];</span>
        <span class="n">vm</span><span class="nf">.replace_module</span><span class="p">(</span><span class="n">new_module</span><span class="p">,</span> <span class="n">initial_data</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
        <span class="n">state</span> <span class="o">=</span> <span class="n">vm</span><span class="nf">.call</span><span class="p">(</span><span class="o">&amp;</span><span class="p">[</span><span class="n">fresh_input</span><span class="p">])</span><span class="o">?</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="n">_</span> <span class="k">=&gt;</span> <span class="p">{</span> <span class="cm">/* continue normal execution */</span> <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Hot code swapping is useful for live development workflows.
Audio engines can replace synthesis scripts
without interrupting playback.
Game scripting systems can reload behavior scripts
without restarting the game loop.</p>

<h2 id="worst-case-analysis">Worst-Case Analysis</h2>

<p>The structural verifier computes
worst-case execution time and worst-case memory usage
for every execution path before the virtual machine starts.
If the verifier cannot prove bounded execution,
the program is rejected.</p>

<h3 id="cost-model">Cost Model</h3>

<p>Each bytecode instruction has an assigned integer cost.
The verifier sums costs along the worst-case path
through each function and each stream iteration.</p>

<p>Common instruction costs include the following.</p>

<table>
  <thead>
    <tr>
      <th>Cost</th>
      <th>Instructions</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>1</td>
      <td>Constants, local variable access, stack operations, control flow delimiters</td>
    </tr>
    <tr>
      <td>2</td>
      <td>Arithmetic, comparisons, indexing, type casts, return</td>
    </tr>
    <tr>
      <td>3</td>
      <td>Division, modulo, field access by name, type tests</td>
    </tr>
    <tr>
      <td>5</td>
      <td>Struct and enum construction, array and tuple allocation</td>
    </tr>
    <tr>
      <td>10</td>
      <td>Function calls, native function calls</td>
    </tr>
  </tbody>
</table>

<h3 id="arena-sizing">Arena Sizing</h3>

<p>The host must provide an arena
large enough for the worst-case memory usage.
Three approaches are available.</p>

<p><strong>Default capacity</strong> is 64 kilobytes.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">arena</span> <span class="o">=</span> <span class="nn">Arena</span><span class="p">::</span><span class="nf">with_capacity</span><span class="p">(</span><span class="n">DEFAULT_ARENA_CAPACITY</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Automatic computation</strong> sizes the arena
to the verified worst-case memory usage.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">cap</span> <span class="o">=</span> <span class="nn">keleusma</span><span class="p">::</span><span class="nn">vm</span><span class="p">::</span><span class="nf">auto_arena_capacity_for</span><span class="p">(</span><span class="o">&amp;</span><span class="n">module</span><span class="p">,</span> <span class="o">&amp;</span><span class="p">[])</span><span class="o">?</span><span class="p">;</span>
<span class="k">let</span> <span class="n">arena</span> <span class="o">=</span> <span class="nn">Arena</span><span class="p">::</span><span class="nf">with_capacity</span><span class="p">(</span><span class="n">cap</span><span class="p">);</span>
</code></pre></div></div>

<p><strong>Static buffer</strong> is suitable for embedded targets
without a heap allocator.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">mut</span> <span class="n">ARENA_BUFFER</span><span class="p">:</span> <span class="p">[</span><span class="nb">u8</span><span class="p">;</span> <span class="mi">16</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">]</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">;</span> <span class="mi">16</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">];</span>
<span class="k">let</span> <span class="n">arena</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span>
    <span class="nn">Arena</span><span class="p">::</span><span class="nf">from_static_buffer</span><span class="p">(</span><span class="nn">core</span><span class="p">::</span><span class="nn">ptr</span><span class="p">::</span><span class="nd">addr_of_mut!</span><span class="p">(</span><span class="n">ARENA_BUFFER</span><span class="p">))</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="compiling-to-bytecode">Compiling to Bytecode</h2>

<p>Scripts can be compiled to bytecode files
for deployment without the source.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>keleusma compile hello.kel <span class="nt">-o</span> hello.kel.bin
</code></pre></div></div>

<p>The host application loads precompiled bytecode
using zero-copy <a href="https://en.wikipedia.org/wiki/Zero-copy">deserialization</a>.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">bytes</span> <span class="o">=</span> <span class="nn">std</span><span class="p">::</span><span class="nn">fs</span><span class="p">::</span><span class="nf">read</span><span class="p">(</span><span class="s">"hello.kel.bin"</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
<span class="k">let</span> <span class="k">mut</span> <span class="n">vm</span> <span class="o">=</span> <span class="nn">Vm</span><span class="p">::</span><span class="nf">load_bytes</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bytes</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">arena</span><span class="p">)</span><span class="o">?</span><span class="p">;</span>
</code></pre></div></div>

<p>Precompiled bytecode is useful
in embedded deployments where source compilation
at runtime is undesirable or impossible.
The bytecode format uses <a href="https://crates.io/crates/rkyv">rkyv</a>
for zero-copy deserialization.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Keleusma provides a scripting language
with static guarantees about termination,
memory usage, and execution time.
The three-function taxonomy of atomic total,
non-atomic total, and productive divergent functions
ensures that the verifier can assign
bounded costs to every execution path.
The <code class="language-plaintext highlighter-rouge">no_std + alloc</code> design,
arena-based memory management,
and precompiled bytecode support
make the virtual machine suitable
for resource-constrained embedded targets.</p>

<p>The language repository
contains additional <a href="https://github.com/sgeos/keleusma/tree/master/examples/scripts">example scripts</a>
demonstrating language features
and <a href="https://github.com/sgeos/keleusma/tree/master/examples">Rust embedding examples</a>
demonstrating host integration patterns
including worst-case memory attestation,
yield-based coroutines,
string handling,
generics,
zero-copy bytecode loading,
and a full <a href="https://wiki.libsdl.org/SDL3/FrontPage">SDL3</a> audio piano roll
with hot code swapping.</p>

<p>The <a href="https://github.com/sgeos/keleusma/tree/master/docs">documentation directory</a>
contains a complete language grammar specification,
instruction set reference,
type system documentation,
execution model description,
and a survey of related work
in synchronous reactive languages,
coalgebraic stream processing,
and worst-case execution time analysis.</p>

<h2 id="future-reading">Future Reading</h2>

<ul>
  <li><a href="https://github.com/sgeos/keleusma/tree/master/docs">Keleusma, Documentation Directory</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/master/docs/architecture/LANGUAGE_DESIGN.md">Keleusma, Language Design</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/master/docs/design/GRAMMAR.md">Keleusma, Grammar Specification</a></li>
  <li><a href="https://github.com/sgeos/keleusma/blob/master/docs/reference/RELATED_WORK.md">Keleusma, Related Work Survey</a></li>
  <li><a href="https://docs.rust-embedded.org/book/">Rust, Embedded Rust Book</a></li>
  <li><a href="https://doi.org/10.1023/B:JUFP.0000020967.94916.1d">Turner, Total Functional Programming (2004)</a></li>
</ul>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Region-based_memory_management">Reference, Arena Allocator</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Bytecode">Reference, Bytecode</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Coroutine">Reference, Coroutine</a></li>
  <li><a href="https://crates.io/crates/keleusma">Reference, Keleusma on crates.io</a></li>
  <li><a href="https://github.com/sgeos/keleusma">Reference, Keleusma on GitHub</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/master/docs">Reference, Keleusma Documentation</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/master/examples/scripts">Reference, Keleusma Example Scripts</a></li>
  <li><a href="https://github.com/sgeos/keleusma/tree/master/examples">Reference, Keleusma Rust Examples</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Pattern_matching">Reference, Pattern Matching</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Productivity_(computer_science)">Reference, Productivity</a></li>
  <li><a href="https://crates.io/crates/rkyv">Reference, rkyv Zero-Copy Serialization</a></li>
  <li><a href="https://wiki.libsdl.org/SDL3/FrontPage">Reference, SDL3</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Stack_machine">Reference, Stack Machine</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Stream_processing">Reference, Stream Processing</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Total_functional_programming">Reference, Total Functional Programming</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Worst-case_execution_time">Reference, Worst-Case Execution Time</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Zero-copy">Reference, Zero-Copy Serialization</a></li>
  <li><a href="/rust/no_std/embedded/2026/01/16/no_std_rust_getting_started.html">Related Post, Getting Started with no_std Rust Programming</a></li>
  <li><a href="https://doi.org/10.1023/B:JUFP.0000020967.94916.1d">Research, Turner (2004), Total Functional Programming</a></li>
  <li><a href="https://doi.org/10.1016/S0304-3975(00)00056-6">Research, Rutten (2000), Universal Coalgebra</a></li>
  <li><a href="https://doi.org/10.1145/1347375.1347389">Research, Wilhelm et al. (2008), WCET Survey</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="embedded" /><category term="programming" /></entry><entry><title type="html">Concentrated Liquidity Market Maker Mathematics</title><link href="https://sgeos.github.io/crypto/defi/rust/2026/02/22/clmm_mathematics.html" rel="alternate" type="text/html" title="Concentrated Liquidity Market Maker Mathematics" /><published>2026-02-22T10:01:47+00:00</published><updated>2026-02-22T10:01:47+00:00</updated><id>https://sgeos.github.io/crypto/defi/rust/2026/02/22/clmm_mathematics</id><content type="html" xml:base="https://sgeos.github.io/crypto/defi/rust/2026/02/22/clmm_mathematics.html"><![CDATA[<!-- A91 -->
<script>console.log("A91");</script>

<p>Concentrated Liquidity Market Makers (CLMMs) extend the Constant Product model
by allowing liquidity providers to allocate capital within a chosen price range
rather than across the entire price domain.
This post deconstructs the core equations of the Concentrated Liquidity model,
the architecture introduced by Uniswap v3 and now the dominant design
in decentralized finance.</p>

<p>This article assumes familiarity with the
<a href="/crypto/defi/rust/2026/01/29/constant_amm_mathematics.html">Constant Product AMM Mathematics</a> article,
which covers the foundational invariant, liquidity, swap execution, fees,
and impermanent loss for the full-range model.
The CLMM extends every one of those concepts
by restricting the active region of the curve to a finite price interval.
This post will first cover CLMM math.
After that, it will present the code for the following <strong>CLMM calculator widget</strong>.</p>

<style>
  .clmm-widget {
    display: grid;
    grid-template-columns: 1fr;
    gap: 12px;
    padding: 20px;
    border: 2px solid #007bff;
    border-radius: 8px;
    max-width: 750px;
    background-color: #ffffff;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  }

  .clmm-widget .clmm-row-1 {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }

  .clmm-widget .clmm-row-3 {
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
    gap: 12px;
  }

  .clmm-widget .clmm-row-slider {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 10px;
    background: #f6f8fa;
    border-radius: 6px;
  }

  .clmm-widget .clmm-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }

  .clmm-widget label {
    font-size: 0.85rem;
    font-weight: 600;
    color: #333;
  }

  .clmm-widget input[type="text"] {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
    width: 100%;
    box-sizing: border-box;
  }

  .clmm-widget input[type="range"] {
    width: 100%;
    margin: 5px 0;
  }

  @media (max-width: 500px) {
    .clmm-widget .clmm-row-3 {
      grid-template-columns: 1fr;
    }
  }
</style>

<script type="module" id="clmm_calculator_ui">
  import init, { inject_ui } from "/assets/wasm/post_clmm_mathematics/post_clmm_mathematics.js";
  async function run() {
    await init();
    inject_ui("clmm_calculator_ui");
  }
  run();
</script>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-02-22 10:01:47 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024<span class="p">;</span> root:xnu-10063.141.2~1/RELEASE_ARM64_T6000 arm64

<span class="nv">$ </span>sw_vers
ProductName:		macOS
ProductVersion:		14.6.1
BuildVersion:		23G93

<span class="c"># Hardware Information</span>
<span class="nv">$ </span>system_profiler SPHardwareDataType | <span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'8,10p'</span>
      Chip: Apple M1 Max
      Total Number of Cores: 10 <span class="o">(</span>8 performance and 2 efficiency<span class="o">)</span>
      Memory: 32 GB

<span class="c"># Shell and Version</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span>
/bin/bash

<span class="nv">$ </span><span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--version</span>  | <span class="nb">head</span> <span class="nt">-n</span> 1
GNU bash, version 3.2.57<span class="o">(</span>1<span class="o">)</span><span class="nt">-release</span> <span class="o">(</span>arm64-apple-darwin23<span class="o">)</span>

<span class="c"># Rust Installation Versions</span>
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.93.0 <span class="o">(</span>083ac5135 2025-12-15<span class="o">)</span>
</code></pre></div></div>

<h2 id="concentrated-liquidity-architecture">Concentrated Liquidity Architecture</h2>

<p>At the core of the Constant Product model is the invariant $x \cdot y = L^2$,
where $x$ and $y$ are the reserves of the two tokens
and $L = \sqrt{x \cdot y}$ is the liquidity.
In a full-range Automated Market Maker (AMM),
capital is spread across the entire price domain from zero to infinity.
The vast majority of that capital sits idle at prices far from the current trading price.</p>

<p>The innovation of concentrated liquidity
is to allow each liquidity provider to choose a finite price interval $[p_a, p_b]$
within which their capital is active.
Within that interval, the position behaves exactly like a constant product AMM.
Outside that interval, the position holds only one token and earns no fees.
The result is that a given amount of capital provides substantially deeper liquidity
around the current price,
improving capital efficiency by orders of magnitude for narrow ranges.</p>

<p>A pool aggregates many individual concentrated positions,
each covering potentially different price intervals.
The aggregate liquidity at any given price
is the sum of all positions whose ranges contain that price.</p>

<h3 id="from-constant-product-to-concentrated-liquidity">From Constant Product to Concentrated Liquidity</h3>

<p>The starting point is the constant product invariant.</p>

\[x \cdot y = L^2\]

<p>The current price $p$ of Token Y in terms of Token X satisfies $p = \frac{y}{x}$.
The reserves can be expressed in terms of $L$ and $p$ as follows.</p>

\[x = \frac{L}{\sqrt{p}} \qquad y = L \cdot \sqrt{p}\]

<p>These are the <strong>virtual reserves</strong> on the full unbounded curve.
They satisfy $x \cdot y = L^2$ and $\frac{y}{x} = p$.</p>

<p>In a concentrated liquidity position with price range $[p_a, p_b]$,
the contract behaves as though the full $x \cdot y = L^2$ curve exists,
but only the segment between prices $p_a$ and $p_b$ is funded with real tokens.
The virtual reserves at any current price $p$ within the range are as follows.</p>

\[x_v(p) = \frac{L}{\sqrt{p}} \qquad y_v(p) = L \cdot \sqrt{p}\]

<p>The <strong>real reserves</strong> are obtained by translating the constant product curve.
The curve segment between $p_a$ and $p_b$ is shifted
so that the real reserves reach zero at the range boundaries.
The translated invariant is as follows.</p>

\[\left(x_r + \frac{L}{\sqrt{p_b}}\right) \cdot \left(y_r + L \cdot \sqrt{p_a}\right) = L^2\]

<p>This is the standard constant product curve
shifted left by $\frac{L}{\sqrt{p_b}}$ and down by $L \cdot \sqrt{p_a}$.
The real reserves $x_r$ and $y_r$ are what the liquidity provider actually deposits and holds.</p>

<blockquote>
  <p><strong>Geometric Interpretation:</strong>
The virtual reserves describe a point on the standard hyperbola $x \cdot y = L^2$.
The real reserves represent the same curve
translated so that the lower-left corner of the active segment sits at the origin.
At price $p_a$, all real reserves are in Token X and $y_r = 0$.
At price $p_b$, all real reserves are in Token Y and $x_r = 0$.</p>
</blockquote>

<h3 id="real-reserves-formulas">Real Reserves Formulas</h3>

<p>When the current price $p$ is within the range $[p_a, p_b]$,
the real reserves are as follows.</p>

\[x_r = L \cdot \left(\frac{1}{\sqrt{p}} - \frac{1}{\sqrt{p_b}}\right) = \frac{L \cdot \left(\sqrt{p_b} - \sqrt{p}\right)}{\sqrt{p} \cdot \sqrt{p_b}}\]

\[y_r = L \cdot \left(\sqrt{p} - \sqrt{p_a}\right)\]

<ul>
  <li>
    <p><strong>$x_r$ (Token X Real Reserve):</strong>
The amount of Token X held by the position.
This quantity decreases as the price rises
and reaches zero when $p = p_b$.
[<strong>Unit:</strong> Token X]</p>
  </li>
  <li>
    <p><strong>$y_r$ (Token Y Real Reserve):</strong>
The amount of Token Y held by the position.
This quantity increases as the price rises
and reaches its maximum when $p = p_b$.
[<strong>Unit:</strong> Token Y]</p>
  </li>
  <li>
    <p><strong>$L$ (Liquidity):</strong>
The liquidity of the position,
identical in definition to the full-range constant product model.
[<strong>Unit:</strong> $\sqrt{\text{Token X} \cdot \text{Token Y}}$]</p>
  </li>
  <li>
    <p><strong>$p_a, p_b$ (Price Range Boundaries):</strong>
The lower and upper bounds of the liquidity position.
[<strong>Unit:</strong> Token Y / Token X]</p>
  </li>
</ul>

<p>These are the core equations.
They correspond to Equations 6.29 and 6.30
in the Uniswap v3 whitepaper <a href="https://app.uniswap.org/whitepaper-v3.pdf">ref_uniswap_v3_whitepaper</a>.</p>

<h3 id="three-price-regimes">Three Price Regimes</h3>

<p>The token amounts held by a position
depend on where the current price $p$ falls relative to the range.
There are three regimes.</p>

<p><strong>Regime 1. Price below range ($p \le p_a$).</strong>
The position is entirely Token X and earns no fees.</p>

\[x_r = \frac{L \cdot \left(\sqrt{p_b} - \sqrt{p_a}\right)}{\sqrt{p_a} \cdot \sqrt{p_b}} \qquad y_r = 0\]

<p><strong>Regime 2. Price within range ($p_a &lt; p &lt; p_b$).</strong>
The position holds both tokens and earns fees on swaps.</p>

\[x_r = \frac{L \cdot \left(\sqrt{p_b} - \sqrt{p}\right)}{\sqrt{p} \cdot \sqrt{p_b}} \qquad y_r = L \cdot \left(\sqrt{p} - \sqrt{p_a}\right)\]

<p><strong>Regime 3. Price above range ($p \ge p_b$).</strong>
The position is entirely Token Y and earns no fees.</p>

\[x_r = 0 \qquad y_r = L \cdot \left(\sqrt{p_b} - \sqrt{p_a}\right)\]

<blockquote>
  <p><strong>Note on Regime Transitions:</strong>
As the price rises through the range from $p_a$ to $p_b$,
the position progressively converts Token X into Token Y.
When the price crosses a boundary,
the position becomes fully denominated in a single token.
If the price later re-enters the range, the position resumes earning fees.</p>
</blockquote>

<h3 id="computing-liquidity-from-a-deposit">Computing Liquidity from a Deposit</h3>

<p>Given a deposit of $\Delta x$ of Token X and $\Delta y$ of Token Y
at current price $p$ within range $[p_a, p_b]$,
the liquidity $L$ is determined by the limiting token.</p>

<p>From the Token X amount, the liquidity implied is as follows.</p>

\[L_x = \frac{\Delta x \cdot \sqrt{p} \cdot \sqrt{p_b}}{\sqrt{p_b} - \sqrt{p}}\]

<p>From the Token Y amount, the liquidity implied is as follows.</p>

\[L_y = \frac{\Delta y}{\sqrt{p} - \sqrt{p_a}}\]

<p>The effective liquidity of the position is the minimum of the two.</p>

\[L = \min(L_x,\ L_y)\]

<p>The minimum is taken because the position is constrained by whichever token is the limiting factor.
Any excess of the other token is returned to the depositor.</p>

<p>When the price is at or below $p_a$, only Token X is required and $L = L_x$.
When the price is at or above $p_b$, only Token Y is required and $L = L_y$.</p>

<blockquote>
  <p><strong>Contrast with Full-Range CPAMM:</strong>
In the Constant Product model,
adding liquidity requires both tokens in proportion to the current price ratio.
In the Concentrated Liquidity model,
the deposit ratio depends on where the current price sits within the chosen range.
At the lower bound, the deposit is entirely Token X.
At the upper bound, the deposit is entirely Token Y.</p>
</blockquote>

<h3 id="tick-mathematics">Tick Mathematics</h3>

<p>Prices in Uniswap v3 are discretized into <strong>ticks</strong>.
Each tick $i$ corresponds to a price as follows.</p>

\[p(i) = 1.0001^i\]

<p>Each tick therefore represents a 0.01% multiplicative change in price,
equivalent to one basis point.
The tick index for a given price is as follows.</p>

\[i = \left\lfloor \frac{\ln(p)}{\ln(1.0001)} \right\rfloor\]

<p>The protocol stores $\sqrt{p}$ rather than $p$ directly
because the key reserve formulas involve $\sqrt{p}$
and storing the square root avoids computing it on-chain.
The square root price from a tick index is as follows.</p>

\[\sqrt{p(i)} = 1.0001^{i/2}\]

<p><strong>Tick spacing</strong> constrains which ticks can serve as position boundaries.
Only tick indices that are multiples of the tick spacing are allowed.
Smaller tick spacing permits more precise ranges
but increases gas costs because each initialized tick crossed during a swap
imposes additional computation.</p>

<p>The standard fee tiers and their corresponding tick spacings are as follows.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Fee Tier</th>
      <th style="text-align: left">Basis Points</th>
      <th style="text-align: left">Tick Spacing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">0.01%</td>
      <td style="text-align: left">1 bp</td>
      <td style="text-align: left">1</td>
    </tr>
    <tr>
      <td style="text-align: left">0.05%</td>
      <td style="text-align: left">5 bp</td>
      <td style="text-align: left">10</td>
    </tr>
    <tr>
      <td style="text-align: left">0.30%</td>
      <td style="text-align: left">30 bp</td>
      <td style="text-align: left">60</td>
    </tr>
    <tr>
      <td style="text-align: left">1.00%</td>
      <td style="text-align: left">100 bp</td>
      <td style="text-align: left">200</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>Note on Governance:</strong>
The 1 basis point fee tier was not in the original Uniswap v3 deployment.
It was added by governance vote in March 2022
to improve competitiveness for stablecoin pairs.</p>
</blockquote>

<h3 id="capital-efficiency">Capital Efficiency</h3>

<p>A concentrated position in range $[p_a, p_b]$
provides the same depth of liquidity
as a full-range position that holds significantly more capital.
The intuition is straightforward.
A full-range position must hold reserves to cover trading at every possible price.
A concentrated position only needs reserves for its chosen range,
so the same dollar amount of capital produces proportionally deeper liquidity within that range.</p>

<p>For a position centered at current price $p$ with range $[p_a, p_b]$,
the capital efficiency relative to a full-range position is approximately as follows.</p>

\[\text{efficiency} \approx \frac{1}{1 - \left(\frac{p_a}{p_b}\right)^{1/4}}\]

<p>For very tight stablecoin ranges,
the efficiency multiplier can reach up to 4000x.
For example, a position in the range 0.999 to 1.001 for a stablecoin pair
provides liquidity equivalent to a full-range position
with thousands of times more capital.</p>

<blockquote>
  <p><strong>Tradeoff:</strong>
Higher capital efficiency comes with higher risk.
A narrower range means the position is more likely to move out of range,
at which point it stops earning fees
and is fully converted to the less valuable token.
Selecting a range is therefore a risk management decision.</p>
</blockquote>

<h3 id="fee-accrual-in-concentrated-liquidity">Fee Accrual in Concentrated Liquidity</h3>

<p>Fees in a CLMM are earned only when the position is in range,
that is, when $p_a \le p \le p_b$.
The protocol tracks fee accumulation using a global variable $f_g$
that records the total fees earned per unit of liquidity since the pool was created.</p>

<p>For each tick boundary,
the protocol tracks $f_o$,
which records fees accumulated on the other side of that tick.
The fees earned within a position’s range are computed as follows.</p>

\[f_{\text{inside}} = f_g - f_{\text{below}}(p_a) - f_{\text{above}}(p_b)\]

<p>The fees owed to a specific position are then as follows.</p>

\[\text{fees} = \left(f_{\text{inside}} - f_{\text{inside,last}}\right) \cdot L\]

<p>The value $f_{\text{inside,last}}$
is the inside fee growth at the time the position was last updated.
A position is updated when it is created, modified, or when fees are collected.
The value $L$ is the position’s liquidity.</p>

<p>This design means that positions only need to be updated
when their tick boundaries are crossed or when the LP interacts with the position,
rather than on every swap.</p>

<h3 id="impermanent-loss-in-concentrated-positions">Impermanent Loss in Concentrated Positions</h3>

<p>For a standard full-range constant product position,
the impermanent loss as a function of the price ratio $r = \frac{p_{\text{new}}}{p_{\text{old}}}$
is as follows.</p>

\[IL(r) = \frac{2\sqrt{r}}{1 + r} - 1\]

<p>Concentrated liquidity amplifies impermanent loss
because the position is effectively leveraged.
The position converts between Tokens X and Y across a narrower price range,
so the same price movement causes a proportionally larger change in portfolio composition.</p>

<p>The value of a concentrated liquidity position at price $p$
when $p_a \le p \le p_b$ is as follows.</p>

\[V(p) = x_r \cdot p + y_r = L \cdot \left(2\sqrt{p} - \frac{p}{\sqrt{p_b}} - \sqrt{p_a}\right)\]

<p>The impermanent loss for a concentrated position
is the difference between this value
and the value of simply holding the initial token quantities at the new price.
Qualitatively, a position that is $N$ times more capital-efficient
also experiences approximately $N$ times more impermanent loss per unit of capital deployed.</p>

<p>Research by Loesch, Hindman, Richardson, and Welch
demonstrates that even when a liquidity range is large enough
to accommodate prices doubling or halving,
the impermanent loss is nearly four times higher
than a full-range position <a href="https://arxiv.org/abs/2111.09192">research_loesch_il</a>.</p>

<blockquote>
  <p><strong>Note on Range Selection:</strong>
The amplification of impermanent loss is the direct cost
of the capital efficiency gained by concentrating liquidity.
A liquidity provider must weigh the additional fee income
earned from deeper liquidity
against the increased impermanent loss risk.</p>
</blockquote>

<h2 id="from-math-to-code">From Math to Code</h2>

<p>The equations above define the theoretical model.
Translating them into code requires handling floating-point arithmetic
and user interaction across multiple input modes.
The following Rust code implements the CLMM calculator widget
shown at the top of this article.
Integration and usage assumes familiarity with Rust-based WASM,
as documented in <a href="/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html">a post on the topic</a>.</p>

<p>The calculator supports three update modes.
Editing the liquidity or price fields recomputes inventories.
Moving the position slider interpolates the current price in $\sqrt{p}$ space.
Editing the inventory fields recomputes the liquidity using the $\min(L_x, L_y)$ formula.</p>

<p><strong><code class="language-plaintext highlighter-rouge">Cargo.toml</code> full listing</strong></p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"post_clmm_mathematics"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2024"</span>

<span class="nn">[lib]</span>
<span class="py">crate-type</span> <span class="p">=</span> <span class="nn">["cdylib"]</span>

<span class="nn">[dependencies]</span>
<span class="py">wasm-bindgen</span> <span class="p">=</span> <span class="s">"0.2.108"</span>
<span class="nn">web-sys</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.3.85"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"Document"</span><span class="p">,</span> <span class="s">"Element"</span><span class="p">,</span> <span class="s">"Event"</span><span class="p">,</span> <span class="s">"EventTarget"</span><span class="p">,</span> <span class="s">"HtmlElement"</span><span class="p">,</span> <span class="s">"HtmlInputElement"</span><span class="p">,</span> <span class="s">"Node"</span><span class="p">,</span> <span class="s">"Window"</span><span class="p">]</span> <span class="p">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">src/lib.rs</code> full listing</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">rc</span><span class="p">::</span><span class="nb">Rc</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="n">JsCast</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">HtmlInputElement</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">ClmmCalculatorInputs</span> <span class="p">{</span>
    <span class="n">liq</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">min_price</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">cur_price</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">max_price</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">slider</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">pos_input</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">x</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">y</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">enum</span> <span class="n">ClmmCalculatorUpdateMode</span> <span class="p">{</span>
    <span class="n">FromPricesAndLiquidity</span><span class="p">,</span>
    <span class="n">FromPositionSlider</span><span class="p">,</span>
    <span class="n">FromReserves</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="n">input</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">HtmlInputElement</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f64</span> <span class="p">{</span>
    <span class="n">input</span><span class="nf">.value</span><span class="p">()</span><span class="py">.parse</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">f64</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">repopulate</span><span class="p">(</span><span class="n">inputs</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">ClmmCalculatorInputs</span><span class="p">,</span> <span class="n">p_a</span><span class="p">:</span> <span class="nb">f64</span><span class="p">,</span> <span class="n">p_c</span><span class="p">:</span> <span class="nb">f64</span><span class="p">,</span> <span class="n">p_b</span><span class="p">:</span> <span class="nb">f64</span><span class="p">,</span> <span class="n">l</span><span class="p">:</span> <span class="nb">f64</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">if</span> <span class="n">p_a</span> <span class="o">&lt;=</span> <span class="mf">0.0</span> <span class="p">||</span> <span class="n">p_b</span> <span class="o">&lt;=</span> <span class="mf">0.0</span> <span class="p">||</span> <span class="n">p_b</span> <span class="o">&lt;=</span> <span class="n">p_a</span> <span class="p">||</span> <span class="n">l</span> <span class="o">&lt;</span> <span class="mf">0.0</span> <span class="p">{</span>
        <span class="k">return</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">inputs</span><span class="py">.min_price</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">p_a</span><span class="p">));</span>
    <span class="n">inputs</span><span class="py">.max_price</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">p_b</span><span class="p">));</span>
    <span class="n">inputs</span><span class="py">.cur_price</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">p_c</span><span class="p">));</span>
    <span class="n">inputs</span><span class="py">.liq</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">l</span><span class="p">));</span>

    <span class="k">let</span> <span class="n">sqrt_a</span> <span class="o">=</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">sqrt_b</span> <span class="o">=</span> <span class="n">p_b</span><span class="nf">.sqrt</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">sqrt_c</span> <span class="o">=</span> <span class="n">p_c</span><span class="nf">.sqrt</span><span class="p">();</span>

    <span class="c1">// Position slider value in sqrt price space</span>
    <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="k">if</span> <span class="n">sqrt_b</span> <span class="o">&gt;</span> <span class="n">sqrt_a</span> <span class="p">{</span>
        <span class="p">((</span><span class="n">sqrt_c</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">))</span><span class="nf">.clamp</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="mf">0.5</span>
    <span class="p">};</span>
    <span class="n">inputs</span><span class="py">.slider</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.4}"</span><span class="p">,</span> <span class="n">pos</span><span class="p">));</span>
    <span class="n">inputs</span><span class="py">.pos_input</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.4}"</span><span class="p">,</span> <span class="n">pos</span><span class="p">));</span>

    <span class="c1">// Token amounts based on price regime</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="o">=</span> <span class="k">if</span> <span class="n">p_c</span> <span class="o">&lt;=</span> <span class="n">p_a</span> <span class="p">{</span>
        <span class="c1">// Below range: all Token X</span>
        <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="n">l</span> <span class="o">*</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_a</span> <span class="o">*</span> <span class="n">sqrt_b</span><span class="p">);</span>
        <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="mf">0.0</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">p_c</span> <span class="o">&gt;=</span> <span class="n">p_b</span> <span class="p">{</span>
        <span class="c1">// Above range: all Token Y</span>
        <span class="k">let</span> <span class="n">y</span> <span class="o">=</span> <span class="n">l</span> <span class="o">*</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">);</span>
        <span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
        <span class="c1">// In range: both tokens</span>
        <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="n">l</span> <span class="o">*</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_c</span><span class="p">)</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_c</span> <span class="o">*</span> <span class="n">sqrt_b</span><span class="p">);</span>
        <span class="k">let</span> <span class="n">y</span> <span class="o">=</span> <span class="n">l</span> <span class="o">*</span> <span class="p">(</span><span class="n">sqrt_c</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">);</span>
        <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="n">inputs</span><span class="py">.x</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.2}"</span><span class="p">,</span> <span class="n">x</span><span class="p">));</span>
    <span class="n">inputs</span><span class="py">.y</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.2}"</span><span class="p">,</span> <span class="n">y</span><span class="p">));</span>
<span class="p">}</span>

<span class="nd">#[wasm_bindgen]</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">inject_ui</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">window</span> <span class="o">=</span> <span class="nn">web_sys</span><span class="p">::</span><span class="nf">window</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing window"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">document</span> <span class="o">=</span> <span class="n">window</span><span class="nf">.document</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing document"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">anchor</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.get_element_by_id</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">)</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing anchor"</span><span class="p">);</span>

    <span class="c1">// UI Container</span>
    <span class="k">let</span> <span class="n">container</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.set_id</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}-container"</span><span class="p">,</span> <span class="n">anchor_id</span><span class="p">));</span>
    <span class="n">container</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="s">"clmm-widget"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Labeled Input Helper</span>
    <span class="k">let</span> <span class="n">create_input</span> <span class="o">=</span>
        <span class="p">|</span><span class="n">id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">label_text</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">input_type</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">css_class</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="nb">str</span><span class="o">&gt;</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">div</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="k">if</span> <span class="k">let</span> <span class="nf">Some</span><span class="p">(</span><span class="n">cls</span><span class="p">)</span> <span class="o">=</span> <span class="n">css_class</span> <span class="p">{</span>
                <span class="n">div</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="n">cls</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">div</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="s">"clmm-field"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="p">}</span>
            <span class="k">let</span> <span class="n">label</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"label"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="n">label</span><span class="nf">.set_text_content</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="n">label_text</span><span class="p">));</span>
            <span class="k">let</span> <span class="n">input</span> <span class="o">=</span> <span class="n">document</span>
                <span class="nf">.create_element</span><span class="p">(</span><span class="s">"input"</span><span class="p">)</span>
                <span class="nf">.unwrap</span><span class="p">()</span>
                <span class="py">.dyn_into</span><span class="p">::</span><span class="o">&lt;</span><span class="n">HtmlInputElement</span><span class="o">&gt;</span><span class="p">()</span>
                <span class="nf">.unwrap</span><span class="p">();</span>
            <span class="n">input</span><span class="nf">.set_id</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}-{}"</span><span class="p">,</span> <span class="n">anchor_id</span><span class="p">,</span> <span class="n">id</span><span class="p">));</span>
            <span class="n">input</span><span class="nf">.set_type</span><span class="p">(</span><span class="n">input_type</span><span class="p">);</span>
            <span class="n">div</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">label</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="n">div</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">input</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
            <span class="p">(</span><span class="n">div</span><span class="p">,</span> <span class="n">input</span><span class="p">)</span>
        <span class="p">};</span>

    <span class="c1">// Row 1: Liquidity</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">liq_div</span><span class="p">,</span> <span class="n">liq_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"liq"</span><span class="p">,</span> <span class="s">"Liquidity Depth (L)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"clmm-row-1"</span><span class="p">));</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">liq_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Row 2: Min Price, Current Price, Max Price</span>
    <span class="k">let</span> <span class="n">price_row</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">price_row</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="s">"clmm-row-3"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">min_div</span><span class="p">,</span> <span class="n">min_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"min"</span><span class="p">,</span> <span class="s">"Min Price (p_a)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">cur_div</span><span class="p">,</span> <span class="n">cur_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"cur"</span><span class="p">,</span> <span class="s">"Current Price (p)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">max_div</span><span class="p">,</span> <span class="n">max_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"max"</span><span class="p">,</span> <span class="s">"Max Price (p_b)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="n">price_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">min_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">price_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cur_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">price_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">max_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">price_row</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Row 3: Position Slider</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">slider_div</span><span class="p">,</span> <span class="n">slider_input</span><span class="p">)</span> <span class="o">=</span>
        <span class="nf">create_input</span><span class="p">(</span><span class="s">"slider"</span><span class="p">,</span> <span class="s">"Position Slider"</span><span class="p">,</span> <span class="s">"range"</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="s">"clmm-row-slider"</span><span class="p">));</span>
    <span class="n">slider_input</span><span class="nf">.set_min</span><span class="p">(</span><span class="s">"0"</span><span class="p">);</span>
    <span class="n">slider_input</span><span class="nf">.set_max</span><span class="p">(</span><span class="s">"1"</span><span class="p">);</span>
    <span class="n">slider_input</span><span class="nf">.set_step</span><span class="p">(</span><span class="s">"0.0001"</span><span class="p">);</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">slider_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Row 4: Token X, Position, Token Y</span>
    <span class="k">let</span> <span class="n">inv_row</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inv_row</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="s">"clmm-row-3"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">x_div</span><span class="p">,</span> <span class="n">x_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"x"</span><span class="p">,</span> <span class="s">"Token X Inventory"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">pos_div</span><span class="p">,</span> <span class="n">pos_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"pos"</span><span class="p">,</span> <span class="s">"Position (0-1)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">y_div</span><span class="p">,</span> <span class="n">y_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"y"</span><span class="p">,</span> <span class="s">"Token Y Inventory"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
    <span class="n">inv_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">x_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inv_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pos_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inv_row</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">y_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inv_row</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Anchor Replacement</span>
    <span class="n">anchor</span>
        <span class="nf">.parent_node</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.replace_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">container</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">anchor</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Shared Input State</span>
    <span class="k">let</span> <span class="n">inputs</span> <span class="o">=</span> <span class="nn">Rc</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">ClmmCalculatorInputs</span> <span class="p">{</span>
        <span class="n">liq</span><span class="p">:</span> <span class="n">liq_input</span><span class="p">,</span>
        <span class="n">min_price</span><span class="p">:</span> <span class="n">min_input</span><span class="p">,</span>
        <span class="n">cur_price</span><span class="p">:</span> <span class="n">cur_input</span><span class="p">,</span>
        <span class="n">max_price</span><span class="p">:</span> <span class="n">max_input</span><span class="p">,</span>
        <span class="n">slider</span><span class="p">:</span> <span class="n">slider_input</span><span class="p">,</span>
        <span class="n">pos_input</span><span class="p">:</span> <span class="n">pos_input</span><span class="p">,</span>
        <span class="n">x</span><span class="p">:</span> <span class="n">x_input</span><span class="p">,</span>
        <span class="n">y</span><span class="p">:</span> <span class="n">y_input</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="c1">// Centralized Update Logic</span>
    <span class="k">let</span> <span class="n">update_widget</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">inputs</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="k">move</span> <span class="p">|</span><span class="n">mode</span><span class="p">:</span> <span class="n">ClmmCalculatorUpdateMode</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">p_a</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.min_price</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">p_b</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.max_price</span><span class="p">);</span>

            <span class="k">match</span> <span class="n">mode</span> <span class="p">{</span>
                <span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromPricesAndLiquidity</span> <span class="k">=&gt;</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">p_c</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.cur_price</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.liq</span><span class="p">);</span>
                    <span class="nf">repopulate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="p">,</span> <span class="n">p_a</span><span class="p">,</span> <span class="n">p_c</span><span class="p">,</span> <span class="n">p_b</span><span class="p">,</span> <span class="n">l</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromPositionSlider</span> <span class="k">=&gt;</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.slider</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">sqrt_a</span> <span class="o">=</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">();</span>
                    <span class="k">let</span> <span class="n">sqrt_b</span> <span class="o">=</span> <span class="n">p_b</span><span class="nf">.sqrt</span><span class="p">();</span>
                    <span class="k">let</span> <span class="n">sqrt_c</span> <span class="o">=</span> <span class="n">sqrt_a</span> <span class="o">+</span> <span class="n">pos</span> <span class="o">*</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">p_c</span> <span class="o">=</span> <span class="n">sqrt_c</span> <span class="o">*</span> <span class="n">sqrt_c</span><span class="p">;</span>
                    <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.liq</span><span class="p">);</span>
                    <span class="nf">repopulate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="p">,</span> <span class="n">p_a</span><span class="p">,</span> <span class="n">p_c</span><span class="p">,</span> <span class="n">p_b</span><span class="p">,</span> <span class="n">l</span><span class="p">);</span>
                <span class="p">}</span>
                <span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromReserves</span> <span class="k">=&gt;</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">p_c</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.cur_price</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.x</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">y</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="py">.y</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">sqrt_a</span> <span class="o">=</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">();</span>
                    <span class="k">let</span> <span class="n">sqrt_b</span> <span class="o">=</span> <span class="n">p_b</span><span class="nf">.sqrt</span><span class="p">();</span>
                    <span class="k">let</span> <span class="n">sqrt_c</span> <span class="o">=</span> <span class="n">p_c</span><span class="nf">.sqrt</span><span class="p">();</span>

                    <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="k">if</span> <span class="n">p_c</span> <span class="o">&lt;=</span> <span class="n">p_a</span> <span class="p">{</span>
                        <span class="n">x</span> <span class="o">*</span> <span class="n">sqrt_a</span> <span class="o">*</span> <span class="n">sqrt_b</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">)</span>
                    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="n">p_c</span> <span class="o">&gt;=</span> <span class="n">p_b</span> <span class="p">{</span>
                        <span class="n">y</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">)</span>
                    <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                        <span class="k">let</span> <span class="n">l_x</span> <span class="o">=</span> <span class="n">x</span> <span class="o">*</span> <span class="n">sqrt_c</span> <span class="o">*</span> <span class="n">sqrt_b</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_b</span> <span class="o">-</span> <span class="n">sqrt_c</span><span class="p">);</span>
                        <span class="k">let</span> <span class="n">l_y</span> <span class="o">=</span> <span class="n">y</span> <span class="o">/</span> <span class="p">(</span><span class="n">sqrt_c</span> <span class="o">-</span> <span class="n">sqrt_a</span><span class="p">);</span>
                        <span class="n">l_x</span><span class="nf">.min</span><span class="p">(</span><span class="n">l_y</span><span class="p">)</span>
                    <span class="p">};</span>
                    <span class="nf">repopulate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="p">,</span> <span class="n">p_a</span><span class="p">,</span> <span class="n">p_c</span><span class="p">,</span> <span class="n">p_b</span><span class="p">,</span> <span class="n">l</span><span class="p">);</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="c1">// Callback Setup</span>
    <span class="k">let</span> <span class="n">update_rc</span> <span class="o">=</span> <span class="nn">Rc</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">update_widget</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">on_price_liq_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromPricesAndLiquidity</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.liq</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_liq_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.min_price</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_liq_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.cur_price</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_liq_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.max_price</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_liq_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_price_liq_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">on_slider_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">ins</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ins</span><span class="py">.slider</span><span class="p">);</span>
            <span class="n">ins</span><span class="py">.pos_input</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.4}"</span><span class="p">,</span> <span class="n">pos</span><span class="p">));</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromPositionSlider</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.slider</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_slider_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_slider_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">on_pos_input_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">ins</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">pos</span> <span class="o">=</span> <span class="nf">parse_f64</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ins</span><span class="py">.pos_input</span><span class="p">)</span><span class="nf">.clamp</span><span class="p">(</span><span class="mf">0.0</span><span class="p">,</span> <span class="mf">1.0</span><span class="p">);</span>
            <span class="n">ins</span><span class="py">.slider</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.4}"</span><span class="p">,</span> <span class="n">pos</span><span class="p">));</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromPositionSlider</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.pos_input</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_pos_input_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_pos_input_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">on_reserve_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">ClmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromReserves</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.x</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_reserve_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.y</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_reserve_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_reserve_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="c1">// Default Values: stablecoin pair range 0.015 to 0.025, position at midpoint</span>
    <span class="k">let</span> <span class="n">p_a</span> <span class="o">=</span> <span class="mf">0.015_f64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">p_b</span> <span class="o">=</span> <span class="mf">0.025_f64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">initial_stable</span> <span class="o">=</span> <span class="mf">1000.0_f64</span><span class="p">;</span>
    <span class="k">let</span> <span class="n">initial_pos</span> <span class="o">=</span> <span class="mf">0.5_f64</span><span class="p">;</span>

    <span class="k">let</span> <span class="n">initial_l</span> <span class="o">=</span> <span class="n">initial_stable</span> <span class="o">/</span> <span class="p">(</span><span class="n">p_b</span><span class="nf">.sqrt</span><span class="p">()</span> <span class="o">-</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">());</span>
    <span class="k">let</span> <span class="n">sqrt_c</span> <span class="o">=</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">()</span> <span class="o">+</span> <span class="n">initial_pos</span> <span class="o">*</span> <span class="p">(</span><span class="n">p_b</span><span class="nf">.sqrt</span><span class="p">()</span> <span class="o">-</span> <span class="n">p_a</span><span class="nf">.sqrt</span><span class="p">());</span>
    <span class="k">let</span> <span class="n">p_c</span> <span class="o">=</span> <span class="n">sqrt_c</span> <span class="o">*</span> <span class="n">sqrt_c</span><span class="p">;</span>

    <span class="nf">repopulate</span><span class="p">(</span><span class="o">&amp;</span><span class="n">inputs</span><span class="p">,</span> <span class="n">p_a</span><span class="p">,</span> <span class="n">p_c</span><span class="p">,</span> <span class="n">p_b</span><span class="p">,</span> <span class="n">initial_l</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Widget JS Injection Anchor Example</strong></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">id=</span><span class="s">"clmm_calculator_ui"</span><span class="nt">&gt;</span>
  <span class="k">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">inject_ui</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">/assets/wasm/post_clmm_mathematics/post_clmm_mathematics.js</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">async</span> <span class="kd">function</span> <span class="nx">run</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">init</span><span class="p">();</span>
    <span class="nx">inject_ui</span><span class="p">(</span><span class="dl">"</span><span class="s2">clmm_calculator_ui</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="nx">run</span><span class="p">();</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p><strong>Widget Inline CSS Styling Example</strong></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
  <span class="nc">.clmm-widget</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
    <span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">2px</span> <span class="nb">solid</span> <span class="m">#007bff</span><span class="p">;</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
    <span class="nl">max-width</span><span class="p">:</span> <span class="m">750px</span><span class="p">;</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="m">#ffffff</span><span class="p">;</span>
    <span class="nl">font-family</span><span class="p">:</span> <span class="n">-apple-system</span><span class="p">,</span> <span class="n">BlinkMacSystemFont</span><span class="p">,</span> <span class="s1">"Segoe UI"</span><span class="p">,</span> <span class="n">Roboto</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nc">.clmm-row-1</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nc">.clmm-row-3</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
    <span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span> <span class="m">1</span><span class="n">fr</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">12px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nc">.clmm-row-slider</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
    <span class="nl">background</span><span class="p">:</span> <span class="m">#f6f8fa</span><span class="p">;</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">6px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nc">.clmm-field</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nt">label</span> <span class="p">{</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="m">0.85rem</span><span class="p">;</span>
    <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="s1">"text"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#ccc</span><span class="p">;</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
    <span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.clmm-widget</span> <span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="s1">"range"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
    <span class="nl">margin</span><span class="p">:</span> <span class="m">5px</span> <span class="m">0</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">@media</span> <span class="p">(</span><span class="n">max-width</span><span class="p">:</span> <span class="m">500px</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">.clmm-widget</span> <span class="nc">.clmm-row-3</span> <span class="p">{</span>
      <span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<p><strong>Example index.html for Local Testing</strong>
Add above CSS styling to commented location.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;!DOCTYPE html&gt;</span>
<span class="nt">&lt;html</span> <span class="na">lang=</span><span class="s">"en"</span><span class="nt">&gt;</span>
<span class="nt">&lt;head&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">charset=</span><span class="s">"UTF-8"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;meta</span> <span class="na">name=</span><span class="s">"viewport"</span> <span class="na">content=</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="nt">&gt;</span>
    <span class="nt">&lt;title&gt;</span>CLMM Calculator<span class="nt">&lt;/title&gt;</span>

    <span class="c">&lt;!-- Inline CSS Here --&gt;</span>
    <span class="nt">&lt;style&gt;&lt;/style&gt;</span>
<span class="nt">&lt;/head&gt;</span>
<span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;h1&gt;</span>CLMM Calculator<span class="nt">&lt;/h1&gt;</span>

    <span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">id=</span><span class="s">"clmm_calculator_ui"</span><span class="nt">&gt;</span>
        <span class="k">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">inject_ui</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">./pkg/post_clmm_mathematics.js</span><span class="dl">"</span><span class="p">;</span>
        <span class="k">async</span> <span class="kd">function</span> <span class="nx">run</span><span class="p">()</span> <span class="p">{</span>
            <span class="k">await</span> <span class="nx">init</span><span class="p">();</span>
            <span class="nx">inject_ui</span><span class="p">(</span><span class="dl">"</span><span class="s2">clmm_calculator_ui</span><span class="dl">"</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="nx">run</span><span class="p">();</span>
    <span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p><strong>Sample Command to Served Example index.html for Local Testing</strong></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Terminal A</span>
<span class="nv">PORT</span><span class="o">=</span><span class="s2">"8000"</span>
python <span class="nt">-m</span> http.server <span class="s2">"</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">"</span>

<span class="nv">PORT</span><span class="o">=</span><span class="s2">"8000"</span>
<span class="c"># Terminal B</span>
open <span class="s2">"http://localhost:</span><span class="k">${</span><span class="nv">PORT</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<h2 id="future-reading">Future Reading</h2>

<p>The mathematics presented in this article cover the core model.
Several directions extend this foundation.</p>

<p><strong>Multi-range strategies.</strong>
Active liquidity management strategies deploy capital across multiple overlapping ranges,
rebalancing as the price moves.
Academic research on optimal range selection remains an active area.</p>

<p><strong>Just-In-Time liquidity.</strong>
Sophisticated participants add concentrated liquidity immediately before a large swap
and remove it immediately after, capturing fees with minimal impermanent loss exposure.
This technique has implications for MEV and fairness.</p>

<p><strong>Options-like payoff analysis.</strong>
The payoff profile of a concentrated liquidity position
resembles a short straddle in options markets.
Research by Loesch et al. formalizes this connection,
and static replication using European options
has been demonstrated for concentrated liquidity positions.</p>

<p><strong>Uniswap v4 hooks.</strong>
The successor protocol introduces a hook system
that allows pool deployers to customize swap logic,
fee structures, and oracle behavior.
This extensibility changes the design space for concentrated liquidity strategies.</p>

<hr />

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://docs.uniswap.org/concepts/protocol/concentrated-liquidity">Reference, Concentrated Liquidity, Uniswap Documentation</a></li>
  <li><a href="https://app.uniswap.org/whitepaper-v3.pdf">Reference, Uniswap v3 Core, Adams, Zinsmeister, Salem, Keefer, and Robinson</a></li>
  <li><a href="/crypto/defi/rust/2026/01/29/constant_amm_mathematics.html">Related Post, Constant Product AMM Mathematics</a></li>
  <li><a href="/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html">Related Post, WASM on a Jekyll Blog with Rust and wasm-bindgen</a></li>
  <li><a href="https://arxiv.org/abs/2111.09192">Research, Impermanent Loss in Uniswap v3, Loesch, Hindman, Richardson, and Welch</a></li>
  <li><a href="https://atiselsts.github.io/pdfs/uniswap-v3-liquidity-math.pdf">Research, Liquidity Math in Uniswap v3, Elsts</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="crypto" /><category term="defi" /><category term="rust" /></entry><entry><title type="html">Constant Product AMM Mathematics</title><link href="https://sgeos.github.io/crypto/defi/rust/2026/01/29/constant_amm_mathematics.html" rel="alternate" type="text/html" title="Constant Product AMM Mathematics" /><published>2026-01-29T17:08:11+00:00</published><updated>2026-01-29T17:08:11+00:00</updated><id>https://sgeos.github.io/crypto/defi/rust/2026/01/29/constant_amm_mathematics</id><content type="html" xml:base="https://sgeos.github.io/crypto/defi/rust/2026/01/29/constant_amm_mathematics.html"><![CDATA[<!-- A73 -->
<script>console.log("A73");</script>

<p>Automated Market Makers (AMMs) revolutionized on-chain liquidity
by replacing central limit order books with deterministic mathematical curves.
This post deconstructs the core equations of the Constant Product model.
This model is the foundation of protocols like Uniswap v2,
covering everything from swap mechanics and fees
to the linear geometry of liquidity and the risks of impermanent loss.</p>

<p>AMM math is also simpler than Concentrated Liquidity Market Maker (CLMM) math.
It is therefore foundational for future study.
This post will first cover AMM math.
After that, it will present the code for the following <strong>AMM calculator widget</strong>.
Other widgets could be written, but this post is sufficiently long as is.</p>

<style>
  .amm-widget {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 15px;
    padding: 20px;
    border: 2px solid red;
    border-radius: 8px;
    max-width: 500px;
    background-color: #ffffff;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  }

  .amm-widget div:nth-child(3) {
    grid-column: 1 / span 2;
    display: flex;
    flex-direction: column;
  }

  .amm-widget div {
    display: flex;
    flex-direction: column;
    gap: 5px;
  }

  .amm-widget label {
    font-size: 0.85rem;
    font-weight: 600;
    color: #333;
  }

  .amm-widget input[type="text"] {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
    width: 100%;
    box-sizing: border-box;
  }

  .amm-widget input[type="range"] {
    width: 100%;
    margin: 10px 0;
  }

  @media (max-width: 400px) {
    .amm-widget {
      grid-template-columns: 1fr;
    }
    .amm-widget div:nth-child(3) {
      grid-column: 1;
    }
  }
</style>

<script type="module" id="amm_calculator_ui">
  import init, { amm_calculator_init } from "/assets/wasm/post_constant_amm_mathematics/post_constant_amm_mathematics.js";
  async function run() {
    await init();
    amm_calculator_init("amm_calculator_ui");
  }
  run();
</script>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-01-29 17:08:11 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024<span class="p">;</span> root:xnu-10063.141.2~1/RELEASE_ARM64_T6000 arm64

<span class="nv">$ </span>sw_vers
ProductName:		macOS
ProductVersion:		14.6.1
BuildVersion:		23G93

<span class="c"># Hardware Information</span>
<span class="nv">$ </span>system_profiler SPHardwareDataType | <span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'8,10p'</span>
      Chip: Apple M1 Max
      Total Number of Cores: 10 <span class="o">(</span>8 performance and 2 efficiency<span class="o">)</span>
      Memory: 32 GB

<span class="c"># Shell and Version</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span>
/bin/bash

<span class="nv">$ </span><span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--version</span>  | <span class="nb">head</span> <span class="nt">-n</span> 1
GNU bash, version 3.2.57<span class="o">(</span>1<span class="o">)</span><span class="nt">-release</span> <span class="o">(</span>arm64-apple-darwin23<span class="o">)</span>

<span class="c"># Rust Installation Versions</span>
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.93.0 <span class="o">(</span>083ac5135 2025-12-15<span class="o">)</span>
</code></pre></div></div>

<h2 id="constant-product-amm-architecture">Constant Product AMM Architecture</h2>

<p>At the core of Decentralized Finance (DeFi)
is the Constant Product Market Maker (CPMM).
This specific branch of mathematics replaces the traditional order book
with an automated peer-to-pool model,
ensuring that a trade can be executed at any time,
regardless of counterparty availability.</p>

<h3 id="constant-product-formula">Constant Product Formula</h3>

<p>The vast majority of AMMs use the <strong>Constant Product Formula</strong>
to model the smart contract liquidity pool curve.
This formula is an industry standard, but its use is not a hard rule.
Any smart contract is free to use whatever model it wants,
but this post assumes the Constant Product Formula.
It dictates that the product of the two asset quantities
must remain constant during any trade.</p>

\[x \cdot y = k\]

<ul>
  <li>
    <p><strong>$x$ (Token X Reserve):</strong>
The total inventory of the first asset (e.g., memecoin or basecoin)
currently held in the AMM smart contract. 
[<strong>Unit:</strong> Token X]</p>
  </li>
  <li>
    <p><strong>$y$ (Token Y Reserve):</strong>
The total inventory of the second asset (e.g., stablecoin or quotecoin)
currently held in the AMM smart contract. 
[<strong>Unit:</strong> Token Y]</p>
  </li>
  <li>
    <p><strong>$k$ (Invariant):</strong>
A fixed value that defines the pool’s pricing curve.
This value is a constant during swaps and only changes when
liquidity is added to or removed from the pool.
[<strong>Unit:</strong> Token X $\cdot$ Token Y]</p>
  </li>
</ul>

<blockquote>
  <p><strong>Base and Quote Convention:</strong>
In financial markets, price is a ratio between two assets.
The asset being priced is the <strong>base</strong>,
and the currency used to measure it is the <strong>quote</strong>.
By convention, price is expressed as
the value of one unit of the base asset in terms of the quote asset.
This maps directly to the AMM formula $P = \frac y x$.
For example, if your price is quoted as <strong>0.007 MEME/USD</strong>, the assignment is:</p>

  <ul>
    <li>x (Base Asset) = MEME</li>
    <li>y (Quote Asset) = USD</li>
  </ul>

  <p>If the price is 0.007 MEME/USD,
your pool will have a large reserve of MEME, and a small reserve of USD.
This is because each MEME is worth very little,
the contract must hold a much higher quantity of them to
maintain the equilibrium of the constant product.</p>
</blockquote>

<blockquote>
  <p><strong>Concrete Example:</strong>
Assume we have a pool that swaps <strong>PIZZA</strong> and <strong>USD</strong>.
Also, assume that the current price of a pizza is <strong>$10</strong>.
Our pool is trading the <strong>PIZZA/USD</strong> pair.
In financial markets, the price is quoted as 10, meaning <strong>10 USD/PIZZA</strong>.
You may see a price of 10 quoted for the <strong>PIZZA/USD</strong> pair,
and this can be very confusing!</p>

  <p>The slash in the pair name is just a separator,
while the slash in the price is a mathematical unit.
In finance, the first asset in a pair is the <strong>base (x)</strong>
and the second is the <strong>quote (y)</strong>.
Because pizza is an expensive asset relative to the dollar,
the reserves are weighted toward the quote:</p>

  <ul>
    <li><strong>1000 Pizza</strong> Base Reserves (x)</li>
    <li><strong>10000 USD</strong> Quote Reserves (y)</li>
    <li><strong>10.00 Price</strong> ($P = \frac y x$)</li>
    <li>~3162.28 Liquidity ($L = \sqrt k = \sqrt{x \cdot y}$)</li>
  </ul>
</blockquote>

<h3 id="liquidity">Liquidity</h3>

<p>In modern liquidity pool implementations,
<strong>Liquidity ($L$)</strong> is defined as the square root of the invariant.
This representation allows for a more linear understanding
of pool depth and simplifies the relationship between the price and reserves.</p>

\[L^2 = k = x \cdot y \quad \Big| \quad L = \sqrt k = \sqrt{x \cdot y} \quad \Big| \quad x = \frac{L}{\sqrt P} \quad \Big| \quad y = L \cdot \sqrt P \quad \Big| \quad P = \frac{y}{x}\]

<ul>
  <li>
    <p><strong>$x, y, k$</strong>: Original reserves and invariant as defined in the previous sections.</p>
  </li>
  <li>
    <p><strong>$L$ (Liquidity):</strong>
A measure of the pool’s “depth.”
This value remains constant during swaps and only changes
when liquidity is added or removed.
[<strong>Unit:</strong> $\sqrt{\text{Token X} \cdot \text{Token Y}}$]</p>
  </li>
  <li>
    <p><strong>$P$ (Spot Price):</strong>
The current marginal exchange rate of the pool.
[<strong>Unit:</strong> Token Y / Token X]</p>
  </li>
</ul>

<blockquote>
  <p><strong>Technical Insight:</strong>
The square root operation effectively reduces the two-dimensional area
of the invariant ($k$) to a single linear dimension ($L$).
This linearizes the relationship between capital and pool depth.
<em>For example, doubling the assets in the pool results in a 2x increase in $L$,
rather than a 4x increase in $k$.</em></p>
</blockquote>

<blockquote>
  <p><strong>Liquidity to Reserve Units:</strong>
L has units of $\sqrt{x \cdot y}$, while the term  $\sqrt P = \sqrt \frac y x$
is used to calculate reserves from the liquidity.
For <strong>y reserves</strong>, the units are
$\frac{\sqrt{x} \cdot \sqrt{y}}{1} \cdot \frac{\sqrt y}{\sqrt x} = y$.
Note that $\frac{1}{\sqrt P}$ is the <strong>reciprocal</strong> of the price,
or $\sqrt{\frac x y}$.
The units for <strong>x reserves</strong> are therefore 
$\frac{\sqrt{x} \cdot \sqrt{y}}{1} \cdot \frac{\sqrt x}{\sqrt y} = x$.</p>
</blockquote>

<h3 id="swap-execution-formula">Swap Execution Formula</h3>

<p>To maintain the invariant ($k$) when a trader interacts with the pool,
we use the <strong>Swap Execution Formula</strong>.
This represents the state of the pool before and after a transaction.
When you perform a swap,
the smart contract calculates exactly how many tokens to move.
This is derived by solving the constant product invariant
for the unknown variable ($\Delta x$ or $\Delta y$).</p>

\[(x - \Delta x) \cdot (y + \Delta y) = k \quad \Big| \quad \Delta x = \frac{x \cdot \Delta y}{y + \Delta y} \quad \Big| \quad \Delta y = \frac{y \cdot \Delta x}{x - \Delta x}\]

<ul>
  <li>
    <p><strong>$x, y, k$</strong>: Original reserves and invariant as defined in the previous sections.</p>
  </li>
  <li>
    <p><strong>$\Delta x$ (Swap Output):</strong>
The number of tokens (e.g., memecoin or basecoin) the trader receives from the pool.
[<strong>Unit:</strong> Token X]</p>
  </li>
  <li>
    <p><strong>$\Delta y$ (Swap Input):</strong>
The number of tokens (e.g., stablecoin or quotecoin) the trader deposits into the pool.
[<strong>Unit:</strong> Token Y]</p>
  </li>
</ul>

<blockquote>
  <p><strong>Note on Slippage:</strong>
Because the denominator of the $\Delta x$ solution includes $\Delta y$,
as the input quantity increases, the output does not grow linearly.
This mathematical reality is what creates <strong>Price Impact</strong>.
The more you buy, the more expensive each subsequent unit becomes.</p>
</blockquote>

<h3 id="swap-execution-formula-with-fees">Swap Execution Formula with Fees</h3>

<p>The fee calculations extend the ideal Swap Execution Formula.
Before the trade is executed,
the pool takes a percentage fee ($f$) of the token input
before performing the constant product calculation.
This creates a divergence between the zero fee <strong>ideal swap</strong>
and the actual <strong>net swap</strong> that hits the trader’s wallet.</p>

<p>The service fee can be set aside for liquidity providers,
or it can be reinvested into the pool,
depending on how the smart contract is set up.
For real world use cases, see your pool documentation.</p>

\[(x - \Delta x_{net}) \cdot (y + \Delta y_{net}) = k \quad \Big| \quad \Delta x_{net} = \frac{x \cdot \Delta x \cdot (1 - f)}{x - \Delta x \cdot f} = \frac{x \cdot \Delta y \cdot (1 - f)}{y + \Delta y \cdot (1 - f)} \quad \Big| \quad \Delta y_{net} = \Delta y \cdot (1 - f) = \frac{y \cdot \Delta x \cdot (1 - f)}{x - \Delta x}\]

<ul>
  <li>
    <p><strong>$x, y, k$</strong>:
Original reserves and invariant as defined in the previous sections.</p>
  </li>
  <li>
    <p><strong>$\Delta x, \Delta y$</strong>:
Ideal output/input values assuming zero fees, as defined in the previous section.</p>
  </li>
  <li>
    <p><strong>$f$ (Fee):</strong> Service fee taken by the pool (e.g., $0.003$ for $0.3\%$). [<strong>Dimensionless</strong>]</p>
  </li>
  <li>
    <p><strong>$\Delta x_{net}$ (Net Swap Output):</strong>
The actual quantity of tokens (e.g., memecoin or basecoin)
the trader receives after fees are deducted from the input.
[<strong>Unit:</strong> Token X]</p>
  </li>
  <li>
    <p><strong>$\Delta y_{net}$ (Net Swap Input):</strong>
The portion of the trader’s deposit (e.g., stablecoin or quotecoin)
that actually shifts the price.
The remainder $(\Delta y \cdot f)$ is the fee collected by the pool.
[<strong>Unit:</strong> Token Y]</p>
  </li>
</ul>

<h3 id="post-trade-price-equations">Post-Trade Price Equations</h3>

<p>After a trade is executed, the balance of the pool has shifted.
Because there are now more of Token Y and fewer of Token X,
the price of Token X increases.
The <strong>new spot price</strong> represents
the marginal price for the very next trader in line.</p>

\[P_{old} = \frac{y}{x} \quad \Big| \quad P_{effective} = \frac{\Delta y}{\Delta x_{net}} \quad \Big| \quad P_{new} = \frac{y + \Delta y}{x - \Delta x_{net}}\]

<ul>
  <li>
    <p><strong>$P_{old}$ (Pre-Trade Spot Price):</strong>
The market price before the transaction occurs.
[<strong>Unit:</strong> Token Y / Token X]</p>
  </li>
  <li>
    <p><strong>$P_{effective}$ (Execution Price):</strong>
The average price paid for the entire swap.
Due to slippage, this is always higher than $P_{old}$ and lower than $P_{new}$.
Note that although the total gross $\Delta y$ is deposited,
only $\Delta x_{net}$ is received by the trader after fees.
[<strong>Unit:</strong> Token Y / Token X]</p>
  </li>
  <li>
    <p><strong>$P_{new}$ (Post-Trade Spot Price):</strong>
The updated marginal price of the pool after the reserves have been rebalanced.
This assumes the fee is reinvested into the pool.
Use $\Delta y_{net}$ in the numerator if fees are paid out to a treasury.
[<strong>Unit:</strong> Token Y / Token X]</p>
  </li>
  <li>
    <p><strong>$y + \Delta y$</strong>:
New Reserve of Token Y with reinvestment.
Original inventory plus the trader’s total deposit.</p>
  </li>
  <li>
    <p><strong>$y + \Delta y_{net}$</strong>:
New Reserve of Token Y without reinvestment.
Original inventory plus the trader’s total deposit less fees.</p>
  </li>
  <li>
    <p><strong>$x - \Delta x_{net}$</strong>:
New Reserve of Token X.
Original inventory minus the trader’s payout.</p>
  </li>
</ul>

<blockquote>
  <p><strong>Note on Price Impact:</strong>
The difference between $P_{old}$ and $P_{effective}$
is what traders refer to as <strong>Price Impact</strong>.
On large trades, the gap between these three prices widens significantly.</p>
</blockquote>

<h3 id="liquidity-provision-equations">Liquidity Provision Equations</h3>

<p>Adding liquidity expands the pool’s reserves of both assets simultaneously.
To avoid shifting the market price during a deposit,
assets must be provided proportionally to the existing ratio.
This action pushes the constant product curve outward,
increasing the pool’s invariant ($k$).</p>

\[\Delta k = (x + \Delta x)(y + \Delta y) - xy \quad \Big| \quad \Delta x = \frac{x \cdot \Delta y}{y} \quad \Big| \quad \Delta y = \frac{y \cdot \Delta x}{x}\]

<ul>
  <li>
    <p><strong>$x, y, k$</strong>:
Original reserves and invariant as defined in the previous sections.</p>
  </li>
  <li>
    <p><strong>$\Delta x$ (Token X Deposit):</strong>
The amount of Token X (e.g., memecoin or basecoin)
provided by the Liquidity Provider.
[<strong>Unit:</strong> Token X]</p>
  </li>
  <li>
    <p><strong>$\Delta y$ (Token Y Deposit):</strong>
The amount of Token Y (e.g., stablecoin or quotecoin)
provided by the Liquidity Provider.
[<strong>Unit:</strong> Token Y]</p>
  </li>
  <li>
    <p><strong>$\Delta k$ (Change in Invariant):</strong>
The increase in the pool’s product.
This represents the added “depth” of the pool.
Generally positive, representing pool growth.
A negative value indicates liquidity withdrawal.
[<strong>Unit:</strong> Token X $\cdot$ Token Y]</p>
  </li>
</ul>

<blockquote>
  <p><strong>Note on Price Stability:</strong>
For a standard deposit, the ratio $\frac{\Delta y}{\Delta x}$
must equal the current spot price $\frac{y}{x}$.
If a provider deposits a disproportionate amount,
they are effectively performing a “swap” against the pool,
shifting the price and incurring slippage before their liquidity is staked.
Single-asset and disproportionate deposits are mathematically possible,
but generally disallowed by AMM smart contracts in practice.</p>
</blockquote>

<h3 id="impermanent-loss-formula">Impermanent Loss Formula</h3>

<p><strong>Impermanent Loss (IL)</strong> is the difference in value between holding
a set of assets in a liquidity pool versus simply holding them
in a private wallet.
It occurs because the AMM’s constant product formula rebalances your
position as prices change,
effectively selling the “winner” and buying more of the “loser.”</p>

\[IL(r) = \frac{2\sqrt{r}}{1+r} - 1\]

<ul>
  <li>
    <p><strong>$IL(r)$ (Impermanent Loss):</strong>
The percentage difference in the total value
of your position compared to holding.
This result is always $\le 0$ (a loss),
though it is “impermanent” because it can disappear
if the price ratio returns to its original state.
Generally expressed as a percentage.
[<strong>Dimensionless</strong>]</p>
  </li>
  <li>
    <p><strong>$r$ (Price Ratio):</strong>
The ratio of the new price to the original price ($P_{new} / P_{old}$).
For example, if the meme coin price doubles, $r = 2$.
[<strong>Dimensionless</strong>]</p>
  </li>
</ul>

<h3 id="il-reference-benchmarks">IL Reference Benchmarks</h3>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Price Example ($P_{new}$)</th>
      <th style="text-align: left">Price Ratio ($r$)</th>
      <th style="text-align: left">Impermanent Loss</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left">0.00700</td>
      <td style="text-align: left">1.00</td>
      <td style="text-align: left">0.0%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.00875</td>
      <td style="text-align: left">1.25</td>
      <td style="text-align: left">-0.6%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.01050</td>
      <td style="text-align: left">1.50</td>
      <td style="text-align: left">-2.0%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.01225</td>
      <td style="text-align: left">1.75</td>
      <td style="text-align: left">-3.8%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.01400</td>
      <td style="text-align: left">2.00</td>
      <td style="text-align: left">-5.7%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.02100</td>
      <td style="text-align: left">3.00</td>
      <td style="text-align: left">-13.4%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.02800</td>
      <td style="text-align: left">4.00</td>
      <td style="text-align: left">-20.0%</td>
    </tr>
    <tr>
      <td style="text-align: left">0.03500</td>
      <td style="text-align: left">5.00</td>
      <td style="text-align: left">-25.5%</td>
    </tr>
  </tbody>
</table>

<blockquote>
  <p><strong>Note on Fees:</strong>
In practice, LPs provide liquidity to earn trading fees.
If the accumulated fees earned during the period are greater than the $IL(r)$,
the liquidity provider still walks away with a net profit.</p>
</blockquote>

<h2 id="from-math-to-code">From Math to Code</h2>

<p>While these equations define the theoretical boundaries of a pool,
translating them into code requires careful handling of fixed-point math
and rounding errors to prevent dust accumulation or drainage.
The code for the AMM calculator is below.
Integration and usage assumes familiarity with Rust-based WASM,
as documented in <a href="/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html">a post I wrote on the topic</a>.</p>

<p><strong><code class="language-plaintext highlighter-rouge">Cargo.toml</code> full listing</strong></p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"post_constant_amm_mathematics"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2024"</span>

<span class="nn">[lib]</span>
<span class="py">crate-type</span> <span class="p">=</span> <span class="nn">["cdylib"]</span>

<span class="nn">[dependencies]</span>
<span class="py">js-sys</span> <span class="p">=</span> <span class="s">"0.3.85"</span>
<span class="py">ra-solana-math</span> <span class="p">=</span> <span class="s">"0.1.1"</span>
<span class="py">wasm-bindgen</span> <span class="p">=</span> <span class="s">"0.2.108"</span>
<span class="nn">web-sys</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.3.85"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"Document"</span><span class="p">,</span> <span class="s">"Element"</span><span class="p">,</span> <span class="s">"Event"</span><span class="p">,</span> <span class="s">"EventTarget"</span><span class="p">,</span> <span class="s">"HtmlElement"</span><span class="p">,</span> <span class="s">"HtmlInputElement"</span><span class="p">,</span> <span class="s">"Node"</span><span class="p">,</span> <span class="s">"Window"</span><span class="p">]</span> <span class="p">}</span>
</code></pre></div></div>

<p><strong><code class="language-plaintext highlighter-rouge">src/lib.rs</code> full listing</strong></p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">ra_solana_math</span><span class="p">::</span><span class="n">FixedPoint</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::</span><span class="nn">rc</span><span class="p">::</span><span class="nb">Rc</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="n">JsCast</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">HtmlInputElement</span><span class="p">;</span>

<span class="k">struct</span> <span class="n">AmmCalculatorInputs</span> <span class="p">{</span>
    <span class="n">liq</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">price</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">slider</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">x</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
    <span class="n">y</span><span class="p">:</span> <span class="n">HtmlInputElement</span><span class="p">,</span>
<span class="p">}</span>

<span class="k">enum</span> <span class="n">AmmCalculatorUpdateMode</span> <span class="p">{</span>
    <span class="n">FromLiqPrice</span><span class="p">,</span>
    <span class="n">FromReserves</span><span class="p">,</span>
<span class="p">}</span>

<span class="nd">#[wasm_bindgen]</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">amm_calculator_init</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">window</span> <span class="o">=</span> <span class="nn">web_sys</span><span class="p">::</span><span class="nf">window</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing window"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">document</span> <span class="o">=</span> <span class="n">window</span><span class="nf">.document</span><span class="p">()</span><span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing document"</span><span class="p">);</span>
    <span class="k">let</span> <span class="n">anchor</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.get_element_by_id</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">)</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"Missing anchor"</span><span class="p">);</span>

    <span class="c1">// UI Container Setup</span>
    <span class="k">let</span> <span class="n">container</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.set_id</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}-container"</span><span class="p">,</span> <span class="n">anchor_id</span><span class="p">));</span>
    <span class="n">container</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"class"</span><span class="p">,</span> <span class="s">"amm-widget"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Labeled Input Helper</span>
    <span class="k">let</span> <span class="n">create_input</span> <span class="o">=</span> <span class="p">|</span><span class="n">id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">label_text</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">,</span> <span class="n">input_type</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">|</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">div</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">label</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"label"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="n">label</span><span class="nf">.set_text_content</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="n">label_text</span><span class="p">));</span>
        <span class="k">let</span> <span class="n">input</span> <span class="o">=</span> <span class="n">document</span>
            <span class="nf">.create_element</span><span class="p">(</span><span class="s">"input"</span><span class="p">)</span>
            <span class="nf">.unwrap</span><span class="p">()</span>
            <span class="py">.dyn_into</span><span class="p">::</span><span class="o">&lt;</span><span class="n">HtmlInputElement</span><span class="o">&gt;</span><span class="p">()</span>
            <span class="nf">.unwrap</span><span class="p">();</span>
        <span class="n">input</span><span class="nf">.set_id</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}-{}"</span><span class="p">,</span> <span class="n">anchor_id</span><span class="p">,</span> <span class="n">id</span><span class="p">));</span>
        <span class="n">input</span><span class="nf">.set_type</span><span class="p">(</span><span class="n">input_type</span><span class="p">);</span>
        <span class="n">div</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">label</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="n">div</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">input</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
        <span class="p">(</span><span class="n">div</span><span class="p">,</span> <span class="n">input</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="k">let</span> <span class="p">(</span><span class="n">liq_div</span><span class="p">,</span> <span class="n">liq_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"liq"</span><span class="p">,</span> <span class="s">"Liquidity (L)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">price_div</span><span class="p">,</span> <span class="n">price_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"price"</span><span class="p">,</span> <span class="s">"Price (P)"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">slider_div</span><span class="p">,</span> <span class="n">slider_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"slider"</span><span class="p">,</span> <span class="s">"Price Slider (Log)"</span><span class="p">,</span> <span class="s">"range"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">x_div</span><span class="p">,</span> <span class="n">x_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"x"</span><span class="p">,</span> <span class="s">"Reserve (X), Meme or Base"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">);</span>
    <span class="k">let</span> <span class="p">(</span><span class="n">y_div</span><span class="p">,</span> <span class="n">y_input</span><span class="p">)</span> <span class="o">=</span> <span class="nf">create_input</span><span class="p">(</span><span class="s">"y"</span><span class="p">,</span> <span class="s">"Reserve (Y), Stable or Quote"</span><span class="p">,</span> <span class="s">"text"</span><span class="p">);</span>

    <span class="c1">// Slider Configuration</span>
    <span class="n">slider_input</span><span class="nf">.set_min</span><span class="p">(</span><span class="s">"-6"</span><span class="p">);</span>
    <span class="n">slider_input</span><span class="nf">.set_max</span><span class="p">(</span><span class="s">"6"</span><span class="p">);</span>
    <span class="n">slider_input</span><span class="nf">.set_step</span><span class="p">(</span><span class="s">"0.01"</span><span class="p">);</span>

    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">liq_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">price_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">slider_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">x_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">y_div</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Anchor Replacement</span>
    <span class="n">anchor</span>
        <span class="nf">.parent_node</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.replace_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">container</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">anchor</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Centralized Update Logic</span>
    <span class="c1">// Wrap inputs in Rc&lt;RefCell&gt; so they can be captured by multiple closures.</span>
    <span class="k">let</span> <span class="n">inputs</span> <span class="o">=</span> <span class="nn">Rc</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">AmmCalculatorInputs</span> <span class="p">{</span>
        <span class="n">liq</span><span class="p">:</span> <span class="n">liq_input</span><span class="p">,</span>
        <span class="n">price</span><span class="p">:</span> <span class="n">price_input</span><span class="p">,</span>
        <span class="n">slider</span><span class="p">:</span> <span class="n">slider_input</span><span class="p">,</span>
        <span class="n">x</span><span class="p">:</span> <span class="n">x_input</span><span class="p">,</span>
        <span class="n">y</span><span class="p">:</span> <span class="n">y_input</span><span class="p">,</span>
    <span class="p">});</span>

    <span class="k">let</span> <span class="n">update_widget</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">inputs</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">zero</span> <span class="o">=</span> <span class="nn">FixedPoint</span><span class="p">::</span><span class="nf">from_int</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> <span class="c1">// Reusable zero</span>
        <span class="k">move</span> <span class="p">|</span><span class="n">mode</span><span class="p">:</span> <span class="n">AmmCalculatorUpdateMode</span><span class="p">|</span> <span class="k">match</span> <span class="n">mode</span> <span class="p">{</span>
            <span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromLiqPrice</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">l_f</span> <span class="o">=</span> <span class="n">inputs</span><span class="py">.liq</span><span class="nf">.value</span><span class="p">()</span><span class="py">.parse</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">f64</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
                <span class="k">let</span> <span class="n">p_f</span> <span class="o">=</span> <span class="n">inputs</span><span class="py">.price</span><span class="nf">.value</span><span class="p">()</span><span class="py">.parse</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">f64</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>

                <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="nn">FixedPoint</span><span class="p">::</span><span class="nf">from_f64</span><span class="p">(</span><span class="n">l_f</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>
                <span class="k">let</span> <span class="n">p</span> <span class="o">=</span> <span class="nn">FixedPoint</span><span class="p">::</span><span class="nf">from_f64</span><span class="p">(</span><span class="n">p_f</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>

                <span class="k">if</span> <span class="k">let</span> <span class="nf">Ok</span><span class="p">(</span><span class="n">sqrt_p</span><span class="p">)</span> <span class="o">=</span> <span class="n">p</span><span class="nf">.sqrt</span><span class="p">()</span> <span class="p">{</span>
                    <span class="k">let</span> <span class="n">x</span> <span class="o">=</span> <span class="n">l</span><span class="nf">.div</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sqrt_p</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>
                    <span class="k">let</span> <span class="n">y</span> <span class="o">=</span> <span class="n">l</span><span class="nf">.mul</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sqrt_p</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>

                    <span class="n">inputs</span>
                        <span class="py">.x</span>
                        <span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">x</span><span class="nf">.to_f64</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)));</span>
                    <span class="n">inputs</span>
                        <span class="py">.y</span>
                        <span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">y</span><span class="nf">.to_f64</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)));</span>

                    <span class="k">let</span> <span class="n">p_float</span> <span class="o">=</span> <span class="n">p</span><span class="nf">.to_f64</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
                    <span class="n">inputs</span><span class="py">.slider</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p_float</span><span class="nf">.log10</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">());</span>
                <span class="p">}</span>
            <span class="p">}</span>
            <span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromReserves</span> <span class="k">=&gt;</span> <span class="p">{</span>
                <span class="k">let</span> <span class="n">x_f</span> <span class="o">=</span> <span class="n">inputs</span><span class="py">.x</span><span class="nf">.value</span><span class="p">()</span><span class="py">.parse</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">f64</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
                <span class="k">let</span> <span class="n">y_f</span> <span class="o">=</span> <span class="n">inputs</span><span class="py">.y</span><span class="nf">.value</span><span class="p">()</span><span class="py">.parse</span><span class="p">::</span><span class="o">&lt;</span><span class="nb">f64</span><span class="o">&gt;</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>

                <span class="k">let</span> <span class="n">l_f</span> <span class="o">=</span> <span class="p">(</span><span class="n">x_f</span> <span class="o">*</span> <span class="n">y_f</span><span class="p">)</span><span class="nf">.sqrt</span><span class="p">();</span>
                <span class="k">let</span> <span class="n">p_f</span> <span class="o">=</span> <span class="k">if</span> <span class="n">x_f</span> <span class="o">!=</span> <span class="mf">0.0</span> <span class="p">{</span> <span class="n">y_f</span> <span class="o">/</span> <span class="n">x_f</span> <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> <span class="mf">0.0</span> <span class="p">};</span>

                <span class="k">let</span> <span class="n">l</span> <span class="o">=</span> <span class="nn">FixedPoint</span><span class="p">::</span><span class="nf">from_f64</span><span class="p">(</span><span class="n">l_f</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>
                <span class="k">let</span> <span class="n">p</span> <span class="o">=</span> <span class="nn">FixedPoint</span><span class="p">::</span><span class="nf">from_f64</span><span class="p">(</span><span class="n">p_f</span><span class="p">)</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="n">zero</span><span class="p">);</span>

                <span class="n">inputs</span>
                    <span class="py">.liq</span>
                    <span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">l</span><span class="nf">.to_f64</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)));</span>
                <span class="n">inputs</span>
                    <span class="py">.price</span>
                    <span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">p</span><span class="nf">.to_f64</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">)));</span>

                <span class="n">inputs</span><span class="py">.slider</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="n">p_f</span><span class="nf">.log10</span><span class="p">()</span><span class="nf">.to_string</span><span class="p">());</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">};</span>

    <span class="c1">// Callback Setup</span>
    <span class="k">let</span> <span class="n">update_rc</span> <span class="o">=</span> <span class="nn">Rc</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">update_widget</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">on_price_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromLiqPrice</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.liq</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.price</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_price_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_price_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">on_slider_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="k">let</span> <span class="n">ins</span> <span class="o">=</span> <span class="n">inputs</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="k">let</span> <span class="n">val</span><span class="p">:</span> <span class="nb">f64</span> <span class="o">=</span> <span class="n">ins</span><span class="py">.slider</span><span class="nf">.value</span><span class="p">()</span><span class="nf">.parse</span><span class="p">()</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="mf">0.0</span><span class="p">);</span>
            <span class="k">let</span> <span class="n">price</span> <span class="o">=</span> <span class="mf">10.0_f64</span><span class="nf">.powf</span><span class="p">(</span><span class="n">val</span><span class="p">);</span>
            <span class="n">ins</span><span class="py">.price</span><span class="nf">.set_value</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{:.6}"</span><span class="p">,</span> <span class="n">price</span><span class="p">));</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromLiqPrice</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>
    <span class="n">inputs</span>
        <span class="py">.slider</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_slider_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_slider_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">on_reserve_change</span> <span class="o">=</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">u</span> <span class="o">=</span> <span class="n">update_rc</span><span class="nf">.clone</span><span class="p">();</span>
        <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_e</span><span class="p">:</span> <span class="nn">web_sys</span><span class="p">::</span><span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
            <span class="nf">u</span><span class="p">(</span><span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromReserves</span><span class="p">);</span>
        <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="o">&gt;</span><span class="p">)</span>
    <span class="p">};</span>

    <span class="n">inputs</span>
        <span class="py">.x</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_reserve_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">inputs</span>
        <span class="py">.y</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">on_reserve_change</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">on_reserve_change</span><span class="nf">.forget</span><span class="p">();</span>

    <span class="c1">// Default Values</span>
    <span class="n">inputs</span><span class="py">.liq</span><span class="nf">.set_value</span><span class="p">(</span><span class="s">"1000000.000000"</span><span class="p">);</span>
    <span class="n">inputs</span><span class="py">.price</span><span class="nf">.set_value</span><span class="p">(</span><span class="s">"0.007000"</span><span class="p">);</span>
    <span class="nf">update_rc</span><span class="p">(</span><span class="nn">AmmCalculatorUpdateMode</span><span class="p">::</span><span class="n">FromLiqPrice</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Widget JS Injection Anchor Example</strong></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">id=</span><span class="s">"amm_calculator_ui"</span><span class="nt">&gt;</span>
  <span class="k">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">amm_calculator_init</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">/assets/wasm/post_constant_amm_mathematics/post_constant_amm_mathematics.js</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">async</span> <span class="kd">function</span> <span class="nx">run</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">init</span><span class="p">();</span>
    <span class="nx">amm_calculator_init</span><span class="p">(</span><span class="dl">"</span><span class="s2">amm_calculator_ui</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="nx">run</span><span class="p">();</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p><strong>Widget Inline CSS Styling Example</strong></p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;style&gt;</span>
  <span class="nc">.amm-widget</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">grid</span><span class="p">;</span>
    <span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">15px</span><span class="p">;</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">2px</span> <span class="nb">solid</span> <span class="no">red</span><span class="p">;</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
    <span class="nl">max-width</span><span class="p">:</span> <span class="m">500px</span><span class="p">;</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="m">#ffffff</span><span class="p">;</span>
    <span class="nl">font-family</span><span class="p">:</span> <span class="n">-apple-system</span><span class="p">,</span> <span class="n">BlinkMacSystemFont</span><span class="p">,</span> <span class="s1">"Segoe UI"</span><span class="p">,</span> <span class="n">Roboto</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.amm-widget</span> <span class="nt">div</span><span class="nd">:nth-child</span><span class="o">(</span><span class="err">3</span><span class="o">)</span> <span class="p">{</span>
    <span class="nl">grid-column</span><span class="p">:</span> <span class="m">1</span> <span class="p">/</span> <span class="n">span</span> <span class="m">2</span><span class="p">;</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.amm-widget</span> <span class="nt">div</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">flex-direction</span><span class="p">:</span> <span class="n">column</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.amm-widget</span> <span class="nt">label</span> <span class="p">{</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="m">0.85rem</span><span class="p">;</span>
    <span class="nl">font-weight</span><span class="p">:</span> <span class="m">600</span><span class="p">;</span>
    <span class="nl">color</span><span class="p">:</span> <span class="m">#333</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.amm-widget</span> <span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="s1">"text"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">8px</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">1px</span> <span class="nb">solid</span> <span class="m">#ccc</span><span class="p">;</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>
    <span class="nl">font-size</span><span class="p">:</span> <span class="m">1rem</span><span class="p">;</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
    <span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.amm-widget</span> <span class="nt">input</span><span class="o">[</span><span class="nt">type</span><span class="o">=</span><span class="s1">"range"</span><span class="o">]</span> <span class="p">{</span>
    <span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
    <span class="nl">margin</span><span class="p">:</span> <span class="m">10px</span> <span class="m">0</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="k">@media</span> <span class="p">(</span><span class="n">max-width</span><span class="p">:</span> <span class="m">400px</span><span class="p">)</span> <span class="p">{</span>
    <span class="nc">.amm-widget</span> <span class="p">{</span>
      <span class="py">grid-template-columns</span><span class="p">:</span> <span class="m">1</span><span class="n">fr</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="nc">.amm-widget</span> <span class="nt">div</span><span class="nd">:nth-child</span><span class="o">(</span><span class="err">3</span><span class="o">)</span> <span class="p">{</span>
      <span class="nl">grid-column</span><span class="p">:</span> <span class="m">1</span><span class="p">;</span>
    <span class="p">}</span>
  <span class="p">}</span>
<span class="nt">&lt;/style&gt;</span>
</code></pre></div></div>

<h2 id="conclusion">Conclusion</h2>

<p>The Constant Product Formula
is a masterpiece of simple yet profound engineering.
By reducing the complexity of a market to a single invariant ($k$),
it created a system where liquidity is always available,
prices are mathematically predictable, and anyone can become a market maker.
Whether you are building a trading bot or a new DEX,
understanding the relationship between reserves, price,
and liquidity is the first step toward mastering DeFi.</p>

<hr />

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://medium.com/auditless/how-to-calculate-impermanent-loss-full-derivation-803e8b2497b7">Impermanent Loss, How to calculate Impermanent Loss: full derivation</a></li>
  <li><a href="https://www.youtube.com/watch?v=_m6Mowq3Ptk">Impermanent Loss, What is Impermanent Loss in Crypto? YouTube</a></li>
  <li><a href="https://www.binance.com/en/academy/articles/impermanent-loss-explained">Impermanent Loss Explained, Binance Academy</a></li>
  <li><a href="/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html">Related Post, WASM on a Jekyll Blog with Rust and wasm-bindgen</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="crypto" /><category term="defi" /><category term="rust" /></entry><entry><title type="html">WASM on a Jekyll Blog with Rust and wasm-bindgen</title><link href="https://sgeos.github.io/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html" rel="alternate" type="text/html" title="WASM on a Jekyll Blog with Rust and wasm-bindgen" /><published>2026-01-26T04:19:39+00:00</published><updated>2026-01-26T04:19:39+00:00</updated><id>https://sgeos.github.io/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll</id><content type="html" xml:base="https://sgeos.github.io/rust/wasm/jekyll/2026/01/26/webasm_on_jekyll.html"><![CDATA[<!-- A72 -->
<script>console.log("A72");</script>

<p>Jekyll generates static pages from templates.
It is often used for blogs, like this one.
Sometimes you want to include interactive elements, like calculators.</p>

<p>This post documents how web assembly (WASM) driven UI widget
can be included in a Jekyll blog post with Rust
and <code class="language-plaintext highlighter-rouge">wasm-bindgen</code>.
It is a tutorial on getting the WASM to work, not a deep dive
into <code class="language-plaintext highlighter-rouge">wasm-bindgen</code>.</p>

<p>The post largely documents the steps taken to get the following
UI-widget up and running on this page.</p>

<style>
  .wasm-greeting-ui {
    max-width: 300px;
    font-family: sans-serif;
    border: 2px solid red;   /* red outline */
    padding: 0.75rem;        /* some space inside the box */
    border-radius: 4px;      /* optional: slightly rounded corners */
    background-color: #fff;  /* optional: white background */
  }

  .wasm-greeting-ui .line1 {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin-bottom: 0.5rem;
  }

  .wasm-greeting-ui .line2 {
    font-weight: bold;
  }
</style>

<script type="module" id="wasm_ui">
  import init, { inject_ui } from "/assets/wasm/post_webasm_on_jekyll/post_webasm_on_jekyll.js";
  async function run() {
    await init();
    inject_ui("wasm_ui");
  }
  run();
</script>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-01-26 04:19:39 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024<span class="p">;</span> root:xnu-10063.141.2~1/RELEASE_ARM64_T6000 arm64

<span class="nv">$ </span>sw_vers
ProductName:		macOS
ProductVersion:		14.6.1
BuildVersion:		23G93

<span class="c"># Hardware Information</span>
<span class="nv">$ </span>system_profiler SPHardwareDataType | <span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'8,10p'</span>
      Chip: Apple M1 Max
      Total Number of Cores: 10 <span class="o">(</span>8 performance and 2 efficiency<span class="o">)</span>
      Memory: 32 GB

<span class="c"># Shell and Version</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span>
/bin/bash

<span class="nv">$ </span><span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--version</span>  | <span class="nb">head</span> <span class="nt">-n</span> 1
GNU bash, version 3.2.57<span class="o">(</span>1<span class="o">)</span><span class="nt">-release</span> <span class="o">(</span>arm64-apple-darwin23<span class="o">)</span>

<span class="c"># Rust Installation Versions</span>
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.93.0 <span class="o">(</span>083ac5135 2025-12-15<span class="o">)</span>
</code></pre></div></div>

<h2 id="instructions">Instructions</h2>

<p>First, create a new library project.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">PROJECT_NAME</span><span class="o">=</span><span class="s2">"post_webasm_on_jekyll"</span>
cargo new <span class="nt">--lib</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">cd</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<p>Next, add the <code class="language-plaintext highlighter-rouge">js-sys</code>, <code class="language-plaintext highlighter-rouge">wasm-bindgen</code>, and <code class="language-plaintext highlighter-rouge">web-sys</code> dependencies for this project.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo add js-sys
cargo add wasm-bindgen
cargo add web-sys <span class="se">\</span>
  <span class="nt">-F</span> Document <span class="nt">-F</span> Element <span class="nt">-F</span> Event <span class="nt">-F</span> EventTarget <span class="se">\</span>
  <span class="nt">-F</span> HtmlElement <span class="nt">-F</span> HtmlInputElement <span class="nt">-F</span> Node <span class="nt">-F</span> Window
</code></pre></div></div>

<p>WASM needs to be packaged as a dynamic library, so
set the library type to a C dynamic library using your editor of choice.</p>

<p><code class="language-plaintext highlighter-rouge">Cargo.toml</code> partial listing</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[lib]</span>
<span class="py">crate-type</span> <span class="p">=</span> <span class="nn">["cdylib"]</span>
</code></pre></div></div>

<p>Your complete <code class="language-plaintext highlighter-rouge">Cargo.toml</code> should look something like this.</p>

<p><code class="language-plaintext highlighter-rouge">Cargo.toml</code> full listing</p>
<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"post_webasm_on_jekyll"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2024"</span>

<span class="nn">[lib]</span>
<span class="py">crate-type</span> <span class="p">=</span> <span class="nn">["cdylib"]</span>

<span class="nn">[dependencies]</span>
<span class="py">js-sys</span> <span class="p">=</span> <span class="s">"0.3.85"</span>
<span class="py">wasm-bindgen</span> <span class="p">=</span> <span class="s">"0.2.108"</span>
<span class="nn">web-sys</span> <span class="o">=</span> <span class="p">{</span> <span class="py">version</span> <span class="p">=</span> <span class="s">"0.3.85"</span><span class="p">,</span> <span class="py">features</span> <span class="p">=</span> <span class="p">[</span><span class="s">"Document"</span><span class="p">,</span> <span class="s">"Element"</span><span class="p">,</span> <span class="s">"Event"</span><span class="p">,</span> <span class="s">"EventTarget"</span><span class="p">,</span> <span class="s">"HtmlElement"</span><span class="p">,</span> <span class="s">"HtmlInputElement"</span><span class="p">,</span> <span class="s">"Node"</span><span class="p">,</span> <span class="s">"Window"</span><span class="p">,</span> <span class="p">]</span> <span class="p">}</span>
</code></pre></div></div>

<p>Replace <code class="language-plaintext highlighter-rouge">src/lib.rs</code> with the following contents.</p>

<p><code class="language-plaintext highlighter-rouge">src/lib.rs</code> full listing</p>
<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="nn">prelude</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">wasm_bindgen</span><span class="p">::</span><span class="n">JsCast</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">web_sys</span><span class="p">::{</span><span class="n">HtmlElement</span><span class="p">,</span> <span class="n">HtmlInputElement</span><span class="p">,</span> <span class="n">Event</span><span class="p">};</span>

<span class="nd">#[wasm_bindgen]</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">inject_ui</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">:</span> <span class="o">&amp;</span><span class="nb">str</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">document</span> <span class="o">=</span> <span class="nn">web_sys</span><span class="p">::</span><span class="nf">window</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.document</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">anchor</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.get_element_by_id</span><span class="p">(</span><span class="n">anchor_id</span><span class="p">)</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"anchor element not found"</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">parent</span> <span class="o">=</span> <span class="n">anchor</span>
        <span class="nf">.parent_node</span><span class="p">()</span>
        <span class="nf">.expect</span><span class="p">(</span><span class="s">"anchor has no parent"</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">container</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.set_class_name</span><span class="p">(</span><span class="s">"wasm-greeting-ui"</span><span class="p">);</span>

    <span class="c1">// --- Line 1: label + input ---</span>
    <span class="k">let</span> <span class="n">line1</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">line1</span><span class="nf">.set_class_name</span><span class="p">(</span><span class="s">"line1"</span><span class="p">);</span>

    <span class="k">let</span> <span class="n">label</span> <span class="o">=</span> <span class="n">document</span><span class="nf">.create_element</span><span class="p">(</span><span class="s">"label"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">label</span><span class="nf">.set_text_content</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="s">"Name: "</span><span class="p">));</span>
    <span class="n">label</span><span class="nf">.set_attribute</span><span class="p">(</span><span class="s">"for"</span><span class="p">,</span> <span class="s">"name-input"</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="k">let</span> <span class="n">input</span><span class="p">:</span> <span class="n">HtmlInputElement</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.create_element</span><span class="p">(</span><span class="s">"input"</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.dyn_into</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">input</span><span class="nf">.set_type</span><span class="p">(</span><span class="s">"text"</span><span class="p">);</span>
    <span class="n">input</span><span class="nf">.set_id</span><span class="p">(</span><span class="s">"name-input"</span><span class="p">);</span>
    <span class="n">input</span><span class="nf">.set_placeholder</span><span class="p">(</span><span class="s">"Enter your name"</span><span class="p">);</span>

    <span class="c1">// append label + input to line1 container</span>
    <span class="n">line1</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">label</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">line1</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">input</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// --- Line 2: message ---</span>
    <span class="k">let</span> <span class="n">message</span><span class="p">:</span> <span class="n">HtmlElement</span> <span class="o">=</span> <span class="n">document</span>
        <span class="nf">.create_element</span><span class="p">(</span><span class="s">"div"</span><span class="p">)</span>
        <span class="nf">.unwrap</span><span class="p">()</span>
        <span class="nf">.dyn_into</span><span class="p">()</span>
        <span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">message</span><span class="nf">.set_class_name</span><span class="p">(</span><span class="s">"line2"</span><span class="p">);</span>

    <span class="nf">update_message</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>

    <span class="c1">// Append line1 and line2 to the main container</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">line1</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>
    <span class="n">container</span><span class="nf">.append_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// Replace the anchor with the new UI</span>
    <span class="n">parent</span><span class="nf">.replace_child</span><span class="p">(</span><span class="o">&amp;</span><span class="n">container</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">anchor</span><span class="p">)</span><span class="nf">.unwrap</span><span class="p">();</span>

    <span class="c1">// --- Event listener ---</span>
    <span class="k">let</span> <span class="n">message_clone</span> <span class="o">=</span> <span class="n">message</span><span class="nf">.clone</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">input_clone</span> <span class="o">=</span> <span class="n">input</span><span class="nf">.clone</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">closure</span> <span class="o">=</span> <span class="nn">Closure</span><span class="p">::</span><span class="nf">wrap</span><span class="p">(</span><span class="nn">Box</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="k">move</span> <span class="p">|</span><span class="n">_event</span><span class="p">:</span> <span class="n">Event</span><span class="p">|</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">value</span> <span class="o">=</span> <span class="n">input_clone</span><span class="nf">.value</span><span class="p">();</span>
        <span class="k">if</span> <span class="n">value</span><span class="nf">.trim</span><span class="p">()</span><span class="nf">.is_empty</span><span class="p">()</span> <span class="p">{</span>
            <span class="nf">update_message</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message_clone</span><span class="p">,</span> <span class="nb">None</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
            <span class="nf">update_message</span><span class="p">(</span><span class="o">&amp;</span><span class="n">message_clone</span><span class="p">,</span> <span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="n">value</span><span class="p">));</span>
        <span class="p">}</span>
    <span class="p">})</span> <span class="k">as</span> <span class="nb">Box</span><span class="o">&lt;</span><span class="k">dyn</span> <span class="nf">FnMut</span><span class="p">(</span><span class="n">Event</span><span class="p">)</span><span class="o">&gt;</span><span class="p">);</span>

    <span class="n">input</span>
        <span class="nf">.add_event_listener_with_callback</span><span class="p">(</span><span class="s">"input"</span><span class="p">,</span> <span class="n">closure</span><span class="nf">.as_ref</span><span class="p">()</span><span class="nf">.unchecked_ref</span><span class="p">())</span>
        <span class="nf">.unwrap</span><span class="p">();</span>

    <span class="n">closure</span><span class="nf">.forget</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">update_message</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">HtmlElement</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">Option</span><span class="o">&lt;&amp;</span><span class="nb">str</span><span class="o">&gt;</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">name</span> <span class="o">=</span> <span class="n">name</span><span class="nf">.unwrap_or</span><span class="p">(</span><span class="s">"USERNAME"</span><span class="p">);</span>
    <span class="n">message</span><span class="nf">.set_text_content</span><span class="p">(</span><span class="nf">Some</span><span class="p">(</span><span class="o">&amp;</span><span class="nd">format!</span><span class="p">(</span><span class="s">"Hello, {}!"</span><span class="p">,</span> <span class="n">name</span><span class="p">)));</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Install <code class="language-plaintext highlighter-rouge">wasm-pack</code> with <code class="language-plaintext highlighter-rouge">cargo</code> if you need to.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo <span class="nb">install </span>wasm-pack
</code></pre></div></div>

<p>Build the WASM with the following command.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wasm-pack build <span class="nt">--target</span> web
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">wasm-pack</code> produces files in <code class="language-plaintext highlighter-rouge">pkg/</code>, which Jekyll will not serve automatically.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tree pkg/
</code></pre></div></div>

<p><strong>Expected Output</strong></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pkg/
├── post_webasm_on_jekyll.d.ts
├── post_webasm_on_jekyll.js
├── post_webasm_on_jekyll_bg.wasm
├── post_webasm_on_jekyll_bg.wasm.d.ts
└── package.json

1 directory, 5 files
</code></pre></div></div>

<p>To serve these files, they need to be copied into
<code class="language-plaintext highlighter-rouge">${JEKYLL_PATH}/assets</code>, ideally in something like the
<code class="language-plaintext highlighter-rouge">wasm/${PROJECT_NAME}</code> subdirectory to keep things organized.
Note that you probably do not want to copy the generated <code class="language-plaintext highlighter-rouge">.gitignore</code>.
Using <code class="language-plaintext highlighter-rouge">pkg/*</code> ensures we copy all build artifacts while ignoring the hidden
<code class="language-plaintext highlighter-rouge">.gitignore</code> file that wasm-pack creates, which would otherwise hide your
WASM files from <code class="language-plaintext highlighter-rouge">git</code> and complicate deployment.
Use the following command to copy the files.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">PROJECT_NAME</span><span class="o">=</span><span class="s2">"post_webasm_on_jekyll"</span>
<span class="nv">JEKYLL_PATH</span><span class="o">=</span><span class="s2">"/path/to/jekyll/blog"</span>
<span class="nb">mkdir</span> <span class="nt">-p</span> <span class="s2">"</span><span class="k">${</span><span class="nv">JEKYLL_PATH</span><span class="k">}</span><span class="s2">/assets/wasm/</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">cp</span> <span class="nt">-r</span> pkg/<span class="k">*</span> <span class="s2">"</span><span class="k">${</span><span class="nv">JEKYLL_PATH</span><span class="k">}</span><span class="s2">/assets/wasm/</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">/"</span>
</code></pre></div></div>

<p>If you want to <strong>overwrite</strong> existing files automatically, add <code class="language-plaintext highlighter-rouge">-f</code>.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> <span class="nt">-rf</span> pkg/<span class="k">*</span> <span class="s2">"</span><span class="k">${</span><span class="nv">JEKYLL_PATH</span><span class="k">}</span><span class="s2">/assets/wasm/</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">/"</span>
</code></pre></div></div>

<p>After copying, all files should be present in the <code class="language-plaintext highlighter-rouge">assets</code> directory.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">ls</span> <span class="nt">-1</span> <span class="s2">"</span><span class="k">${</span><span class="nv">JEKYLL_PATH</span><span class="k">}</span><span class="s2">/assets/wasm/</span><span class="k">${</span><span class="nv">PROJECT_NAME</span><span class="k">}</span><span class="s2">/"</span>
</code></pre></div></div>

<p><strong>Expected Output</strong></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package.json
post_webasm_on_jekyll.d.ts
post_webasm_on_jekyll.js
post_webasm_on_jekyll_bg.wasm
post_webasm_on_jekyll_bg.wasm.d.ts
</code></pre></div></div>

<p>Inline an HTML <code class="language-plaintext highlighter-rouge">script</code> tag in the blog post to load the WASM. 
Note that the <code class="language-plaintext highlighter-rouge">id</code> of the <code class="language-plaintext highlighter-rouge">script</code> tag is used as an anchor, 
which the Rust code will replace with the WASM-driven UI. 
Alternatively, you can use an empty <code class="language-plaintext highlighter-rouge">div</code> (<code class="language-plaintext highlighter-rouge">&lt;div id="wasm_ui"&gt;&lt;/div&gt;</code>) 
or a self-closing tag like <code class="language-plaintext highlighter-rouge">&lt;input type="hidden" id="wasm_ui" /&gt;</code> 
as your anchor if you prefer to keep the script logic and the 
UI placement separate.</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"module"</span> <span class="na">id=</span><span class="s">"wasm_ui"</span><span class="nt">&gt;</span>
  <span class="k">import</span> <span class="nx">init</span><span class="p">,</span> <span class="p">{</span> <span class="nx">inject_ui</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">/assets/wasm/post_webasm_on_jekyll/post_webasm_on_jekyll.js</span><span class="dl">"</span><span class="p">;</span>
  <span class="k">async</span> <span class="kd">function</span> <span class="nx">run</span><span class="p">()</span> <span class="p">{</span>
    <span class="k">await</span> <span class="nx">init</span><span class="p">();</span>
    <span class="nx">inject_ui</span><span class="p">(</span><span class="dl">"</span><span class="s2">wasm_ui</span><span class="dl">"</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="nx">run</span><span class="p">();</span>
<span class="nt">&lt;/script&gt;</span>
</code></pre></div></div>

<p>Also, you can add CSS in a <code class="language-plaintext highlighter-rouge">style</code> block above the <code class="language-plaintext highlighter-rouge">script</code> tag,
or put it in an included external file if you want to style your UI.</p>

<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="nt">style</span><span class="o">&gt;</span>
  <span class="nc">.wasm-greeting-ui</span> <span class="p">{</span>
    <span class="nl">max-width</span><span class="p">:</span> <span class="m">300px</span><span class="p">;</span>
    <span class="nl">font-family</span><span class="p">:</span> <span class="nb">sans-serif</span><span class="p">;</span>
    <span class="nl">border</span><span class="p">:</span> <span class="m">2px</span> <span class="nb">solid</span> <span class="no">red</span><span class="p">;</span>   <span class="c">/* red outline */</span>
    <span class="nl">padding</span><span class="p">:</span> <span class="m">0.75rem</span><span class="p">;</span>        <span class="c">/* some space inside the box */</span>
    <span class="nl">border-radius</span><span class="p">:</span> <span class="m">4px</span><span class="p">;</span>      <span class="c">/* optional: slightly rounded corners */</span>
    <span class="nl">background-color</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span>  <span class="c">/* optional: white background */</span>
  <span class="p">}</span>

  <span class="nc">.wasm-greeting-ui</span> <span class="nc">.line1</span> <span class="p">{</span>
    <span class="nl">display</span><span class="p">:</span> <span class="n">flex</span><span class="p">;</span>
    <span class="nl">align-items</span><span class="p">:</span> <span class="nb">center</span><span class="p">;</span>
    <span class="py">gap</span><span class="p">:</span> <span class="m">0.5rem</span><span class="p">;</span>
    <span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">0.5rem</span><span class="p">;</span>
  <span class="p">}</span>

  <span class="nc">.wasm-greeting-ui</span> <span class="nc">.line2</span> <span class="p">{</span>
    <span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span>
  <span class="p">}</span>
<span class="o">&lt;/</span><span class="nt">style</span><span class="o">&gt;</span>
</code></pre></div></div>

<h2 id="future-reading">Future Reading</h2>

<p>If you want to explore more about WebAssembly, Rust, and integrating dynamic
UIs in static sites, these resources are highly recommended:</p>

<ul>
  <li>
    <p><strong>Rust and WASM Fundamentals:</strong>
Learn how Rust compiles to WebAssembly, how to use <code class="language-plaintext highlighter-rouge">wasm-bindgen</code>, and best
practices for building interactive web apps. See the official
<a href="https://rustwasm.github.io/docs/book/">Rust and WebAssembly Book</a>.</p>
  </li>
  <li>
    <p><strong>Jekyll Assets &amp; Static Sites:</strong>
Tips on structuring static assets in Jekyll, managing JavaScript/CSS, and
optimizing for WASM loading can be found in the
<a href="https://jekyllrb.com/docs/assets/">Jekyll Assets Documentation</a>.</p>
  </li>
</ul>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://rustwasm.github.io/docs/book/">Rust and WebAssembly Book</a></li>
  <li><a href="https://jekyllrb.com/docs/assets/">Jekyll Assets</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="wasm" /><category term="jekyll" /></entry><entry><title type="html">Getting Started with no_std Rust Programming</title><link href="https://sgeos.github.io/rust/no_std/embedded/2026/01/16/no_std_rust_getting_started.html" rel="alternate" type="text/html" title="Getting Started with no_std Rust Programming" /><published>2026-01-16T21:40:17+00:00</published><updated>2026-01-16T21:40:17+00:00</updated><id>https://sgeos.github.io/rust/no_std/embedded/2026/01/16/no_std_rust_getting_started</id><content type="html" xml:base="https://sgeos.github.io/rust/no_std/embedded/2026/01/16/no_std_rust_getting_started.html"><![CDATA[<!-- A68 -->
<script>console.log("A68");</script>

<p><code class="language-plaintext highlighter-rouge">no_std</code> <a href="https://www.rust-lang.org">Rust</a> programming involves developing applications
without relying on Rust’s standard library (<code class="language-plaintext highlighter-rouge">std</code>). This constraint is typical
in embedded systems, where resources are limited or when direct control over
hardware is required. Why use <code class="language-plaintext highlighter-rouge">no_std</code> Rust?</p>

<ul>
  <li><strong>Bare-Metal Requirement</strong>: <code class="language-plaintext highlighter-rouge">std</code> assumes an operating system, and it has not
been ported to many specialized systems.</li>
  <li><strong>Cross-Platform Compatibility</strong>: Enables development for various architectures 
and platforms without the overhead or assumptions of the standard library.</li>
  <li><strong>Resource Efficiency</strong>: Operates in environments with limited memory and 
storage.</li>
  <li><strong>Direct Hardware Control</strong>: Allows developers to write software that directly 
interacts with hardware.</li>
</ul>

<p>This post introduces <code class="language-plaintext highlighter-rouge">no_std</code> Rust, providing the foundation you need
to start your journey in the world of embedded development with Rust. After that,
it covers how the <code class="language-plaintext highlighter-rouge">no_std</code> <code class="language-plaintext highlighter-rouge">core</code> and <code class="language-plaintext highlighter-rouge">alloc</code> functionality is exposed to <code class="language-plaintext highlighter-rouge">std</code>
Rust programs before giving pointers to more in-depth information.</p>

<p>This post assumes a general familiarity with Rust, <code class="language-plaintext highlighter-rouge">rustup</code> and <code class="language-plaintext highlighter-rouge">cargo</code>.
Readers who are not familiar with Rust and the ecosystem tooling should consider
starting with “<a href="https://doc.rust-lang.org/book/">The Rust Programming Language Book</a>”.
(<a href="https://rust-lang.github.io/rustup/">rustup</a> and <a href="https://doc.rust-lang.org/cargo/">cargo</a> also have extensive
documentation.)</p>

<h2 id="software-versions">Software Versions</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Date (UTC)</span>
<span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2026-01-16 21:40:17 +0000

<span class="c"># OS and Version</span>
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 23.6.0: Mon Jul 29 21:14:30 PDT 2024<span class="p">;</span> root:xnu-10063.141.2~1/RELEASE_ARM64_T6000 arm64

<span class="nv">$ </span>sw_vers
ProductName:		macOS
ProductVersion:		14.6.1
BuildVersion:		23G93

<span class="c"># Hardware Information</span>
<span class="nv">$ </span>system_profiler SPHardwareDataType | <span class="nb">sed</span> <span class="nt">-n</span> <span class="s1">'8,10p'</span>
      Chip: Apple M1 Max
      Total Number of Cores: 10 <span class="o">(</span>8 performance and 2 efficiency<span class="o">)</span>
      Memory: 32 GB

<span class="c"># Shell and Version</span>
<span class="nv">$ </span><span class="nb">echo</span> <span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span>
/bin/bash

<span class="nv">$ </span><span class="s2">"</span><span class="k">${</span><span class="nv">SHELL</span><span class="k">}</span><span class="s2">"</span> <span class="nt">--version</span>  | <span class="nb">head</span> <span class="nt">-n</span> 1
GNU bash, version 3.2.57<span class="o">(</span>1<span class="o">)</span><span class="nt">-release</span> <span class="o">(</span>arm64-apple-darwin23<span class="o">)</span>

<span class="c"># Rust Installation Versions</span>
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.92.0 <span class="o">(</span>344c4567c 2025-10-21<span class="o">)</span>
</code></pre></div></div>

<h2 id="tutorial-objective">Tutorial Objective</h2>

<p>This tutorial aims to guide you through creating a <code class="language-plaintext highlighter-rouge">no_std</code> Rust library that performs fundamental rocket calculations. Once the library is developed, we will demonstrate its use through a simple binary application that utilizes these computations.</p>

<h3 id="thrust-equation">Thrust Equation</h3>

<p>The thrust $T$, generated by a rocket, is calculated by the following equation:</p>

\[T = \dot{m} \times v_e + (p_e - p_0) \times A_e\]

<p>where:</p>
<ul>
  <li>$T$ is the thrust, measured in Newtons ($\text{N}$),</li>
  <li>$\dot{m}$ is the mass flow rate of the propellant, measured in $\frac{\text{kg}}{\text{s}}$,</li>
  <li>$v_e$ is the exhaust velocity, measured in $\frac{\text{m}}{\text{s}}$,</li>
  <li>$p_e$ is the pressure at the nozzle exit, measured in Pascals ($\text{Pa}$),</li>
  <li>$p_0$ is the ambient pressure, measured in Pascals ($\text{Pa}$),</li>
  <li>$A_e$ is the exit area of the nozzle, measured in square meters ($\text{m}^2$).</li>
</ul>

<h3 id="specific-impulse-equation">Specific Impulse Equation</h3>

<p>The specific impulse $I_{sp}$, which indicates the efficiency of rocket propellants, can be derived from the thrust and the mass flow rate as follows:</p>

\[I_{sp} = \frac{T}{\dot{m} \times g_0}\]

<p>where:</p>
<ul>
  <li>$I_{sp}$ is the specific impulse, measured in seconds ($\text{s}$),</li>
  <li>$T$ is the thrust, measured in Newtons ($\text{N}$),</li>
  <li>$\dot{m}$ is the mass flow rate of the propellant, measured in $\frac{\text{kg}}{\text{s}}$,</li>
  <li>$g_0$ is the standard acceleration due to gravity, approximately $9.80665 \, \frac{\text{m}}{\text{s}^2}$.</li>
</ul>

<h3 id="delta-v-equation">Delta-v Equation</h3>

<p>The delta-v $\Delta v$, or change in velocity that a rocket can achieve, is calculated using the Tsiolkovsky rocket equation:</p>

\[\Delta v = I_{sp} \times g_0 \times \ln\left(\frac{m_0}{m_f}\right)\]

<p>where:</p>
<ul>
  <li>$\Delta v$ is the change in velocity, measured in $\frac{\text{m}}{\text{s}}$,</li>
  <li>$I_{sp}$ is the specific impulse, measured in seconds ($\text{s}$),</li>
  <li>$m_0$ is the initial total mass of the rocket, including propellant, measured in kilograms ($\text{kg}$),</li>
  <li>$m_f$ is the final mass of the rocket after the propellant has been expended, measured in kilograms ($\text{kg}$),</li>
  <li>$\ln$ represents the natural logarithm.</li>
</ul>

<h2 id="creating-a-no_std-rust-library">Creating a no_std Rust Library</h2>

<p>Start by creating a new Rust project using <code class="language-plaintext highlighter-rouge">cargo</code>.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">PROJECT</span><span class="o">=</span><span class="s2">"no_std_example"</span>
cargo new <span class="nt">--lib</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span><span class="s2">"</span>
<span class="nb">cd</span> <span class="s2">"</span><span class="k">${</span><span class="nv">PROJECT</span><span class="k">}</span><span class="s2">"</span>
</code></pre></div></div>

<p>In your <code class="language-plaintext highlighter-rouge">lib.rs</code>, declare the project as <code class="language-plaintext highlighter-rouge">no_std</code> and start coding.
In the following example, note that the
<a href="https://www1.grc.nasa.gov/beginners-guide-to-aeronautics/ideal-rocket-equation/">ideal rocket equation</a> includes a
natural logarithm, so <code class="language-plaintext highlighter-rouge">ln</code> needs to be pulled in from <code class="language-plaintext highlighter-rouge">libm</code>.
Neither the <a href="https://www.grc.nasa.gov/www/k-12/airplane/specimp.html">specific impulse equation</a>
nor the <a href="https://www.grc.nasa.gov/www/k-12/airplane/rockth.html">rocket thrust equation</a> use anything beyond
arithmetic, so they do not require the dependency.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/lib.rs</span>

<span class="nd">#![no_std]</span>

<span class="k">extern</span> <span class="k">crate</span> <span class="n">libm</span><span class="p">;</span>

<span class="cd">/// Standard gravity (m/s^2)</span>
<span class="k">pub</span> <span class="k">const</span> <span class="n">G0</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">9.80665</span><span class="p">;</span>

<span class="cd">/// Calculates the thrust given the mass flow rate of the propellant (m dot),</span>
<span class="cd">/// the exhaust velocity (Ve), the pressure at the nozzle exit (Pe),</span>
<span class="cd">/// the ambient pressure (P0), and the exit area of the nozzle (Ae).</span>
<span class="cd">///</span>
<span class="cd">/// # Arguments</span>
<span class="cd">///</span>
<span class="cd">/// * `m_dot` - Mass flow rate of the propellant (kg/s).</span>
<span class="cd">/// * `ve` - Exhaust velocity (m/s).</span>
<span class="cd">/// * `pe` - Pressure at the nozzle exit (Pa).</span>
<span class="cd">/// * `p0` - Ambient pressure (Pa).</span>
<span class="cd">/// * `ae` - Exit area of the nozzle (m^2).</span>
<span class="cd">///</span>
<span class="cd">/// # Returns</span>
<span class="cd">///</span>
<span class="cd">/// Thrust in Newtons.</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">calculate_thrust</span><span class="p">(</span><span class="n">m_dot</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">ve</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">pe</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">p0</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">ae</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
  <span class="n">m_dot</span> <span class="o">*</span> <span class="n">ve</span> <span class="o">+</span> <span class="p">(</span><span class="n">pe</span> <span class="o">-</span> <span class="n">p0</span><span class="p">)</span> <span class="o">*</span> <span class="n">ae</span>
<span class="p">}</span>

<span class="cd">/// Calculates the specific impulse given the thrust and the mass flow rate.</span>
<span class="cd">///</span>
<span class="cd">/// # Arguments</span>
<span class="cd">///</span>
<span class="cd">/// * `thrust` - The thrust in Newtons (N).</span>
<span class="cd">/// * `m_dot` - The mass flow rate in kilograms per second (kg/s).</span>
<span class="cd">///</span>
<span class="cd">/// # Returns</span>
<span class="cd">///</span>
<span class="cd">/// * Specific impulse in seconds (s).</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">calculate_specific_impulse</span><span class="p">(</span><span class="n">thrust</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">m_dot</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
  <span class="n">thrust</span> <span class="o">/</span> <span class="p">(</span><span class="n">m_dot</span> <span class="o">*</span> <span class="n">G0</span><span class="p">)</span>
<span class="p">}</span>

<span class="cd">/// Calculates the delta-v of a rocket using the Tsiolkovsky rocket equation.</span>
<span class="cd">///</span>
<span class="cd">/// # Arguments</span>
<span class="cd">///</span>
<span class="cd">/// * `isp` - The specific impulse in seconds (s).</span>
<span class="cd">/// * `m0` - The initial total mass of the rocket (including propellant) in kilograms (kg).</span>
<span class="cd">/// * `mf` - The final mass of the rocket (without propellant) in kilograms (kg).</span>
<span class="cd">///</span>
<span class="cd">/// # Returns</span>
<span class="cd">///</span>
<span class="cd">/// * Delta-v in meters per second (m/s).</span>
<span class="k">pub</span> <span class="k">fn</span> <span class="nf">calculate_delta_v</span><span class="p">(</span><span class="n">isp</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">m0</span><span class="p">:</span> <span class="nb">f32</span><span class="p">,</span> <span class="n">mf</span><span class="p">:</span> <span class="nb">f32</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">f32</span> <span class="p">{</span>
  <span class="n">isp</span> <span class="o">*</span> <span class="n">G0</span> <span class="o">*</span> <span class="nn">libm</span><span class="p">::</span><span class="nf">logf</span><span class="p">(</span><span class="n">m0</span> <span class="o">/</span> <span class="n">mf</span><span class="p">)</span>
<span class="p">}</span>

<span class="nd">#[cfg(test)]</span>
<span class="k">mod</span> <span class="n">tests</span> <span class="p">{</span>
    <span class="k">use</span> <span class="k">super</span><span class="p">::</span><span class="o">*</span><span class="p">;</span>

    <span class="k">const</span> <span class="n">ACCEPTABLE_ERROR</span><span class="p">:</span> <span class="nb">f32</span> <span class="o">=</span> <span class="mf">1e-3</span><span class="p">;</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_calculate_thrust</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">m_dot</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">;</span> <span class="c1">// Mass flow rate (kg/s)</span>
        <span class="k">let</span> <span class="n">ve</span> <span class="o">=</span> <span class="mf">2500.0</span><span class="p">;</span> <span class="c1">// Exhaust velocity (m/s)</span>
        <span class="k">let</span> <span class="n">pe</span> <span class="o">=</span> <span class="mf">101325.0</span><span class="p">;</span> <span class="c1">// Pressure at nozzle exit (Pa) - Atmospheric pressure</span>
        <span class="k">let</span> <span class="n">p0</span> <span class="o">=</span> <span class="mf">101325.0</span><span class="p">;</span> <span class="c1">// Ambient pressure (Pa) - Atmospheric pressure</span>
        <span class="k">let</span> <span class="n">ae</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">;</span> <span class="c1">// Exit area of the nozzle (m^2)</span>
        <span class="k">let</span> <span class="n">expected_thrust</span> <span class="o">=</span> <span class="mf">12500.0</span><span class="p">;</span> <span class="c1">// Expected thrust (N)</span>

        <span class="k">let</span> <span class="n">thrust</span> <span class="o">=</span> <span class="nf">calculate_thrust</span><span class="p">(</span><span class="n">m_dot</span><span class="p">,</span> <span class="n">ve</span><span class="p">,</span> <span class="n">pe</span><span class="p">,</span> <span class="n">p0</span><span class="p">,</span> <span class="n">ae</span><span class="p">);</span>
        <span class="c1">// Ensure the calculated thrust is as expected</span>
        <span class="nd">assert!</span><span class="p">(</span> <span class="p">(</span><span class="n">thrust</span> <span class="o">-</span> <span class="n">expected_thrust</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">ACCEPTABLE_ERROR</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_calculate_specific_impulse</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">thrust</span> <span class="o">=</span> <span class="mf">12500.0</span><span class="p">;</span> <span class="c1">// Thrust (N)</span>
        <span class="k">let</span> <span class="n">m_dot</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">;</span> <span class="c1">// Mass flow rate (kg/s)</span>
        <span class="k">let</span> <span class="n">expected_isp</span> <span class="o">=</span> <span class="mf">254.929</span><span class="p">;</span> <span class="c1">// Expected specific impulse (s)</span>

        <span class="k">let</span> <span class="n">isp</span> <span class="o">=</span> <span class="nf">calculate_specific_impulse</span><span class="p">(</span><span class="n">thrust</span><span class="p">,</span> <span class="n">m_dot</span><span class="p">);</span>
        <span class="c1">// Ensure the calculated thrust is as expected</span>
        <span class="nd">assert!</span><span class="p">(</span> <span class="p">(</span><span class="n">isp</span> <span class="o">-</span> <span class="n">expected_isp</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">ACCEPTABLE_ERROR</span><span class="p">);</span>
    <span class="p">}</span>

    <span class="nd">#[test]</span>
    <span class="k">fn</span> <span class="nf">test_calculate_delta_v</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">let</span> <span class="n">isp</span> <span class="o">=</span> <span class="mf">254.6479</span><span class="p">;</span> <span class="c1">// Specific impulse (s)</span>
        <span class="k">let</span> <span class="n">m0</span> <span class="o">=</span> <span class="mf">1000.0</span><span class="p">;</span> <span class="c1">// Initial mass of the rocket (kg)</span>
        <span class="k">let</span> <span class="n">mf</span> <span class="o">=</span> <span class="mf">100.0</span><span class="p">;</span> <span class="c1">// Final mass of the rocket (kg)</span>
        <span class="k">let</span> <span class="n">expected_delta_v</span> <span class="o">=</span> <span class="mf">5750.114</span><span class="p">;</span> <span class="c1">// Expected delta-v (m/s)</span>

        <span class="k">let</span> <span class="n">delta_v</span> <span class="o">=</span> <span class="nf">calculate_delta_v</span><span class="p">(</span><span class="n">isp</span><span class="p">,</span> <span class="n">m0</span><span class="p">,</span> <span class="n">mf</span><span class="p">);</span>
        <span class="c1">// Ensure the calculated delta-v is as expected</span>
        <span class="nd">assert!</span><span class="p">(</span> <span class="p">(</span><span class="n">delta_v</span> <span class="o">-</span> <span class="n">expected_delta_v</span><span class="p">)</span><span class="nf">.abs</span><span class="p">()</span> <span class="o">&lt;</span> <span class="n">ACCEPTABLE_ERROR</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Modify your <code class="language-plaintext highlighter-rouge">Cargo.toml</code> to pull in the <code class="language-plaintext highlighter-rouge">libm</code> dependency.
Also, specify the <code class="language-plaintext highlighter-rouge">panic = "abort"</code> strategy.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>cargo add libm
<span class="nv">$ </span><span class="nb">cat</span> <span class="o">&lt;&lt;</span><span class="no">EOF</span><span class="sh"> &gt;&gt; Cargo.toml

[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"
</span><span class="no">EOF
</span></code></pre></div></div>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cargo.toml</span>

<span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"no_std_example"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2021"</span>

<span class="nn">[dependencies]</span>
<span class="py">libm</span> <span class="p">=</span> <span class="s">"0.2.15"</span>

<span class="nn">[profile.dev]</span>
<span class="py">panic</span> <span class="p">=</span> <span class="s">"abort"</span>

<span class="nn">[profile.release]</span>
<span class="py">panic</span> <span class="p">=</span> <span class="s">"abort"</span>
</code></pre></div></div>

<p>Test the library.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>cargo <span class="nb">test</span>
</code></pre></div></div>

<h2 id="using-the-library">Using the Library</h2>

<p>Next, add a <strong>src/main.rs</strong> file that uses the library.</p>

<div class="language-rust highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// src/main.rs</span>

<span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>

<span class="c1">// Change the following line to use your crate name.</span>
<span class="k">use</span> <span class="n">no_std_example</span> <span class="k">as</span> <span class="n">crate_library</span><span class="p">;</span>
<span class="k">use</span> <span class="n">libc_print</span><span class="p">;</span>

<span class="nd">#[unsafe(no_mangle)]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">(</span><span class="n">_argc</span><span class="p">:</span> <span class="nb">i32</span><span class="p">,</span> <span class="n">_argv</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="o">*</span><span class="k">const</span> <span class="nb">u8</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">i32</span> <span class="p">{</span>
    <span class="c1">// Example values for thrust calculation</span>
    <span class="k">let</span> <span class="n">m_dot</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">;</span> <span class="c1">// Mass flow rate in kg/s</span>
    <span class="k">let</span> <span class="n">ve</span> <span class="o">=</span> <span class="mf">2500.0</span><span class="p">;</span> <span class="c1">// Exhaust velocity in m/s</span>
    <span class="k">let</span> <span class="n">pe</span> <span class="o">=</span> <span class="mf">101325.0</span><span class="p">;</span> <span class="c1">// Pressure at nozzle exit in Pa</span>
    <span class="k">let</span> <span class="n">p0</span> <span class="o">=</span> <span class="mf">101325.0</span><span class="p">;</span> <span class="c1">// Ambient pressure in Pa</span>
    <span class="k">let</span> <span class="n">ae</span> <span class="o">=</span> <span class="mf">0.1</span><span class="p">;</span> <span class="c1">// Exit area of the nozzle in m^2</span>

    <span class="c1">// Calculating thrust</span>
    <span class="k">let</span> <span class="n">thrust</span> <span class="o">=</span> <span class="nn">crate_library</span><span class="p">::</span><span class="nf">calculate_thrust</span><span class="p">(</span><span class="n">m_dot</span><span class="p">,</span> <span class="n">ve</span><span class="p">,</span> <span class="n">pe</span><span class="p">,</span> <span class="n">p0</span><span class="p">,</span> <span class="n">ae</span><span class="p">);</span>
    <span class="nn">libc_print</span><span class="p">::</span><span class="nd">libc_println!</span><span class="p">(</span><span class="s">"Thrust: {} N"</span><span class="p">,</span> <span class="n">thrust</span><span class="p">);</span>

    <span class="c1">// Calculating specific impulse</span>
    <span class="k">let</span> <span class="n">isp</span> <span class="o">=</span> <span class="nn">crate_library</span><span class="p">::</span><span class="nf">calculate_specific_impulse</span><span class="p">(</span><span class="n">thrust</span><span class="p">,</span> <span class="n">m_dot</span><span class="p">);</span>
    <span class="nn">libc_print</span><span class="p">::</span><span class="nd">libc_println!</span><span class="p">(</span><span class="s">"Specific Impulse: {} s"</span><span class="p">,</span> <span class="n">isp</span><span class="p">);</span>

    <span class="c1">// Example values for delta-v calculation</span>
    <span class="k">let</span> <span class="n">m0</span> <span class="o">=</span> <span class="mf">1000.0</span><span class="p">;</span> <span class="c1">// Initial total mass in kg</span>
    <span class="k">let</span> <span class="n">mf</span> <span class="o">=</span> <span class="mf">100.0</span><span class="p">;</span> <span class="c1">// Final mass in kg</span>

    <span class="c1">// Calculating delta-v</span>
    <span class="k">let</span> <span class="n">delta_v</span> <span class="o">=</span> <span class="nn">crate_library</span><span class="p">::</span><span class="nf">calculate_delta_v</span><span class="p">(</span><span class="n">isp</span><span class="p">,</span> <span class="n">m0</span><span class="p">,</span> <span class="n">mf</span><span class="p">);</span>
    <span class="nn">libc_print</span><span class="p">::</span><span class="nd">libc_println!</span><span class="p">(</span><span class="s">"Delta-v: {} m/s"</span><span class="p">,</span> <span class="n">delta_v</span><span class="p">);</span>

    <span class="c1">// Return success</span>
    <span class="mi">0</span>
<span class="p">}</span>

<span class="c1">// Panic handling in module for easy conditional exclusion when testing.</span>
<span class="c1">// Testing pulls in std, which provides panic handling.</span>
<span class="k">mod</span> <span class="n">panic_handling</span> <span class="p">{</span>
    <span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">panic</span><span class="p">::</span><span class="n">PanicInfo</span><span class="p">;</span>

    <span class="nd">#[panic_handler]</span>
    <span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_info</span><span class="p">:</span> <span class="o">&amp;</span><span class="n">PanicInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
        <span class="k">loop</span> <span class="p">{}</span>
    <span class="p">}</span>

    <span class="c1">// dummy symbol hack for my `aarch64-apple-darwin` host</span>
    <span class="nd">#[unsafe(no_mangle)]</span>
    <span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">rust_eh_personality</span><span class="p">()</span> <span class="p">{}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>We are using <code class="language-plaintext highlighter-rouge">libc_print</code> for output in the above binary, so add it as a
dependency in <strong>Cargo.toml</strong>.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>cargo add libc-print
</code></pre></div></div>

<div class="language-toml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Cargo.toml, partial listing</span>
<span class="nn">[dependencies]</span>
<span class="py">libc-print</span> <span class="p">=</span> <span class="s">"0.1.23"</span>
</code></pre></div></div>

<p>Generally speaking, <code class="language-plaintext highlighter-rouge">libc</code> can used in conjunction with <code class="language-plaintext highlighter-rouge">no_std</code> code to
test its functionality on the development host machine. Note that <code class="language-plaintext highlighter-rouge">libc</code>
is not always available on development targets. Some systems have a
proprietary library that implements key functionality generally found in
<code class="language-plaintext highlighter-rouge">libc</code>, while others have a quirky proprietary <code class="language-plaintext highlighter-rouge">libc</code>. In these cases,
it is safe to assume that <code class="language-plaintext highlighter-rouge">libc</code> related crates cannot be used.</p>

<p>You can run the <code class="language-plaintext highlighter-rouge">no_std</code> binary on the host machine with the following
command.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cargo run
</code></pre></div></div>

<p>Assuming you are using a modern Stable toolchain and the <code class="language-plaintext highlighter-rouge">panic = "abort"</code>
profile is set, the output should look something like this.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Thrust: 12500 N
Specific Impulse: 254.92906 s
Delta-v: 5756.4634 m/s
</code></pre></div></div>

<p>This concludes the tutorial section of the post.</p>

<h2 id="debugging-and-testing">Debugging and Testing</h2>

<p>As illustrated above, standard Rust <a href="https://doc.rust-lang.org/book/ch11-00-testing.html">unit</a> and
<a href="https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html">integration testing</a> can be
used to validate <code class="language-plaintext highlighter-rouge">no_std</code> code.
You might need to simulate or mock the hardware interactions.
<a href="https://doc.rust-lang.org/cargo/guide/continuous-integration.html">Continuous integration</a>
can also be used to ensure that
every change is validated on both host and target platforms.</p>

<p><a href="https://docs.rs/cargo-embed/latest/cargo_embed/util/rtt/index.html">Real Time Transfer (RTT)</a> is supported by
<a href="https://probe.rs">probe-rs</a> and it can be used to log the state of code
running on an embedded device.
<code class="language-plaintext highlighter-rouge">cargo-embed</code>, another <code class="language-plaintext highlighter-rouge">probe-rs</code> tool, even supports on device
<a href="https://docs.rs/cargo-embed/latest/cargo_embed/">GDB debugging</a>.</p>

<p>Emulators like <a href="https://www.qemu.org/docs/master/system/index.html">QEMU</a>
can be used to simulate your target hardware environment,
which is especially useful for early development stages.
Tools like JTAG, SWD, and hardware debuggers can be used to
<a href="http://openocd.org/doc-release/html/GDB-and-OpenOCD.html">directly interact</a>
with applications running on actual hardware.</p>

<h2 id="moving-from-std-to-no_std-rust">Moving From std to no_std Rust</h2>

<p>Most crates are written assuming <code class="language-plaintext highlighter-rouge">std</code> Rust.
These crates can simply not be used in a <code class="language-plaintext highlighter-rouge">no_std</code> environment.
<code class="language-plaintext highlighter-rouge">no_std</code> alternatives will need to be identified, and you may need to write
your own implemention if your desired functionality is unavailable.</p>

<p>When a program is declard <code class="language-plaintext highlighter-rouge">no_std</code>, it automatically pulls in the <code class="language-plaintext highlighter-rouge">core</code> crate.
There is no way around this. <code class="language-plaintext highlighter-rouge">no_std</code> <code class="language-plaintext highlighter-rouge">core</code> is Rust’s minimum feature set.
Everything that relies on heap allocated memory is located in <code class="language-plaintext highlighter-rouge">alloc</code>.
Note that you need to supply your own memory allocation strategy when using
<code class="language-plaintext highlighter-rouge">alloc</code> in a <code class="language-plaintext highlighter-rouge">no_std</code> environment, but there are crates like
<code class="language-plaintext highlighter-rouge">embedded_alloc</code> for this.</p>

<p>The good news is that <code class="language-plaintext highlighter-rouge">std</code> effectively reexports everything <code class="language-plaintext highlighter-rouge">core</code> and <code class="language-plaintext highlighter-rouge">alloc</code>.
Therefore, unlike popular third party crates, a lot of the types you are
accustomed to using are actually available in a <code class="language-plaintext highlighter-rouge">no_std</code> environment,
especially if <code class="language-plaintext highlighter-rouge">core</code> and <code class="language-plaintext highlighter-rouge">alloc</code> are available.
There are generally drop in replacements for the types defined in <code class="language-plaintext highlighter-rouge">std</code>.
The following table broadly illustrates what is reexported by <code class="language-plaintext highlighter-rouge">std</code> and
from where.</p>

<table>
  <thead>
    <tr>
      <th style="text-align: left">Type Category</th>
      <th style="text-align: left">Located In</th>
      <th style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code> Re-export</th>
      <th style="text-align: left">Notes</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td style="text-align: left"><strong>Primitives</strong> (<code class="language-plaintext highlighter-rouge">u8</code>, <code class="language-plaintext highlighter-rouge">f32</code>, etc.)</td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">core</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left">Always available.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Option/Result</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">core</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left">Always available.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Iterators/Markers</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">core</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Copy</code>, <code class="language-plaintext highlighter-rouge">Send</code>, <code class="language-plaintext highlighter-rouge">Sync</code>, <code class="language-plaintext highlighter-rouge">Iterator</code>.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>String / Vec</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">alloc</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left">Requires a <code class="language-plaintext highlighter-rouge">#[global_allocator]</code>.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Smart Pointers</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">alloc</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">Box</code>, <code class="language-plaintext highlighter-rouge">Arc</code>, <code class="language-plaintext highlighter-rouge">Rc</code>.</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>BTreeMap/BTreeSet</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">alloc</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">HashMap</code> is NOT in <code class="language-plaintext highlighter-rouge">alloc</code> (needs OS entropy).</td>
    </tr>
    <tr>
      <td style="text-align: left"><strong>Networking/Files</strong></td>
      <td style="text-align: left"><code class="language-plaintext highlighter-rouge">std</code></td>
      <td style="text-align: left">N/A</td>
      <td style="text-align: left">Not available in <code class="language-plaintext highlighter-rouge">no_std</code>.</td>
    </tr>
  </tbody>
</table>

<p><code class="language-plaintext highlighter-rouge">BTreeMap</code> and <code class="language-plaintext highlighter-rouge">BTreeSet</code> are the standard functional alternatives to <code class="language-plaintext highlighter-rouge">HashMap</code>
and <code class="language-plaintext highlighter-rouge">HashSet</code> when working in a <code class="language-plaintext highlighter-rouge">core</code> and <code class="language-plaintext highlighter-rouge">alloc</code> environment.
The <code class="language-plaintext highlighter-rouge">hashbrown</code> crate is a commonly used <code class="language-plaintext highlighter-rouge">no_std</code> <code class="language-plaintext highlighter-rouge">HashMap</code> alternative.
It is actually the underlying implementation of <code class="language-plaintext highlighter-rouge">std::collections::HashMap</code>.
Note that you must provide your own non-random hasher, like <code class="language-plaintext highlighter-rouge">FxHash</code> because
an OS to provide random seeds is unavailable.</p>

<h2 id="nightly-rust">Nightly Rust</h2>

<p>In the past, nightly Rust was required to build <code class="language-plaintext highlighter-rouge">no_std</code> binaries.
It is still required for tier 3 targets where the <code class="language-plaintext highlighter-rouge">-Z build-std</code> flag needs
to be used to build <code class="language-plaintext highlighter-rouge">core</code> and <code class="language-plaintext highlighter-rouge">alloc</code> from scratch for your chip.</p>

<p>Outside of this toy example, you might need to do other things that require
nightly Rust.
For example, it was required in the past because a proper custom implemention
of <code class="language-plaintext highlighter-rouge">eh_personality</code> needed to be supplied, as opposed to the symbol hack I
used in this post.
You might need to use other unstable features, or advanced <code class="language-plaintext highlighter-rouge">asm!</code> feature..</p>

<p>The following commands can be used to install the nightly toolchain for
the host system and switch to it.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>rustup toolchain <span class="nb">install </span>nightly
<span class="nv">$ </span>rustup default nightly
</code></pre></div></div>

<p>The following command can be used to switch back to stable.</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>rustup default stable
</code></pre></div></div>

<h2 id="next-steps">Next Steps</h2>

<p>The <a href="https://docs.rust-embedded.org/book/">Embedded Rust Book</a> is an excellent resource for
those looking to deepen their understanding of embedded systems programming
with Rust.
It includes a variety of examples and extensive discussions on interfacing
with hardware.
Additionally, this book provides a list of useful resources that can help
further your learning.</p>

<p>For those eager to purchase hardware to learn on, it’s wise to identify a
learning resource and specifically purchase the hardware it recommends.
For example, the <a href="https://docs.rust-embedded.org/discovery/">Discovery Book</a> is highly recommended
for beginners.
It provides step-by-step instructions and ready-to-run examples that are
extremely helpful when you are just starting out.
These examples are tailored for specific development boards, ensuring
that you can follow along without compatibility issues given the right
hardware.
Alternatively, the <a href="https://www.youtube.com/watch?v=TOAynddiu5M">embedded Rust setup explained</a>
YouTube video is a good option for those who prefer video tutorials.</p>

<h3 id="additional-tools-and-libraries">Additional Tools and Libraries</h3>

<p>The embedded Rust ecosystem is enriched by a variety of libraries that
enhance functionality and ease development across different hardware
platforms.
Below is an overview of some essential libraries, listed alphabetically:</p>

<ul>
  <li>
    <p><strong><a href="https://crates.io/crates/cargo-embed">cargo-embed</a></strong>: Facilitates flashing and debugging of
embedded applications, leveraging <code class="language-plaintext highlighter-rouge">probe-rs</code> for its backend, and includes
support for RTT (Real-Time Transfer).</p>
  </li>
  <li>
    <p><strong><a href="https://github.com/drone-os/drone">drone-os</a></strong>: An embedded operating system written in Rust
that runs on ARM Cortex-M processors, suitable for high-performance real-time
applications.</p>
  </li>
  <li>
    <p><strong><a href="https://github.com/embassy-rs/embassy">Embassy</a></strong>: An asynchronous runtime for embedded systems
that aims to make using async/await feasible in no_std environments, with
support for a variety of embedded platforms.</p>
  </li>
  <li>
    <p><strong><a href="https://github.com/rust-embedded/embedded-hal">embedded-hal</a></strong>: Provides hardware abstraction
layers for interfacing with peripherals across different microcontrollers,
facilitating portable and reusable code.</p>
  </li>
  <li>
    <p><strong><a href="https://crates.io/crates/heapless">heapless</a></strong>: Enables the use of static memory allocation
for data structures like vectors, strings, and hash maps, crucial for
systems where dynamic memory allocation cannot be used.</p>
  </li>
  <li>
    <p><strong><a href="https://probe.rs">probe-rs</a></strong>: A modern debugging and flashing tool that
supports a wide range of ARM Cortex-M microcontrollers.</p>
  </li>
  <li>
    <p><strong><a href="https://rtic.rs">RTIC (Real-Time Interrupt-driven Concurrency)</a></strong>: Offers a
concurrency framework for building real-time systems in Rust, which is
ideal for applications needing high reliability and performance.</p>
  </li>
  <li>
    <p><strong><a href="https://github.com/jonas-schievink/rubble">rubble</a></strong>: A Bluetooth stack implemented in pure Rust,
designed for embedded systems using Bluetooth Low Energy.</p>
  </li>
  <li>
    <p><strong><a href="https://crates.io/crates/svd2rust">svd2rust</a></strong>: Generates Rust API from SVD (System View
Description) files, allowing for safe, ergonomic interaction with
peripheral registers.</p>
  </li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p><code class="language-plaintext highlighter-rouge">no_std</code> Rust programming opens up a new realm of possibilities for Rust 
developers in the embedded world. By understanding the basics and progressively 
exploring more complex scenarios, you can leverage Rust’s safety and 
performance on platforms where the standard library is not an option.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://www1.grc.nasa.gov/beginners-guide-to-aeronautics/ideal-rocket-equation/">NASA, Ideal Rocket Equation</a></li>
  <li><a href="https://www.grc.nasa.gov/www/k-12/airplane/specimp.html">NASA, Rocket Specific Impulse Equation</a></li>
  <li><a href="https://www.grc.nasa.gov/www/k-12/airplane/rockth.html">NASA, Rocket Thrust Equation</a></li>
  <li><a href="http://openocd.org/doc-release/html/GDB-and-OpenOCD.html">OpenOCD, Debugging</a></li>
  <li><a href="https://www.qemu.org/docs/master/system/index.html">QEMU, Guide</a></li>
  <li><a href="https://doc.rust-lang.org/cargo/">Rust, Cargo Book</a></li>
  <li><a href="https://crates.io/crates/cargo-embed">Rust, cargo-embed on crates.io</a></li>
  <li><a href="https://doc.rust-lang.org/cargo/guide/continuous-integration.html">Rust, Continuous Integration</a></li>
  <li><a href="https://docs.rust-embedded.org/discovery/">Rust, Discovery Book</a></li>
  <li><a href="https://github.com/drone-os/drone">Rust, drone-os GitHub Repository</a></li>
  <li><a href="https://github.com/embassy-rs/embassy">Rust, Embassy GitHub Repository</a></li>
  <li><a href="https://github.com/rust-embedded/embedded-hal">Rust, embedded-hal Repository</a></li>
  <li><a href="https://www.youtube.com/watch?v=TOAynddiu5M">Rust, Embedded Rust Setup Explained YouTube Video</a></li>
  <li><a href="https://docs.rust-embedded.org/embedonomicon/">Rust, Embedonomicon</a></li>
  <li><a href="https://docs.rs/cargo-embed/latest/cargo_embed/">Rust, GDB Debugging with cargo-embed</a></li>
  <li><a href="https://crates.io/crates/heapless">Rust, heapless on crates.io</a></li>
  <li><a href="https://www.rust-lang.org">Rust, Home Page</a></li>
  <li><a href="https://probe.rs">Rust, probe-rs GitHub Repository</a></li>
  <li><a href="https://probe.rs">Rust, probe-rs: A Modern, High-Performance Debugging Toolkit</a></li>
  <li><a href="https://docs.rs/cargo-embed/latest/cargo_embed/util/rtt/index.html">Rust, Real-Time Transfer (RTT) with cargo-embed</a></li>
  <li><a href="https://github.com/jonas-schievink/rubble">Rust, rubble GitHub Repository</a></li>
  <li><a href="https://doc.rust-lang.org/book/ch11-00-testing.html">Rust, Rust Book - Testing</a></li>
  <li><a href="https://doc.rust-lang.org/rust-by-example/testing/integration_testing.html">Rust, Rust by Example - Integration Testing</a></li>
  <li><a href="https://docs.rust-embedded.org/book/">Rust, Rust Embedded Book</a></li>
  <li><a href="https://doc.rust-lang.org/book/">Rust, Rust Programming Language</a></li>
  <li><a href="https://rtic.rs">Rust, RTIC GitHub Repository</a></li>
  <li><a href="https://rust-lang.github.io/rustup/">Rust, rustup Book</a></li>
  <li><a href="https://crates.io/crates/svd2rust">Rust, svd2rust on crates.io</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="no_std" /><category term="embedded" /></entry><entry><title type="html">Getting Started with Cargo Workspaces for Rust Development</title><link href="https://sgeos.github.io/rust/no_std/2022/12/01/cargo_workspaces_for_rust.html" rel="alternate" type="text/html" title="Getting Started with Cargo Workspaces for Rust Development" /><published>2022-12-01T18:34:26+00:00</published><updated>2022-12-01T18:34:26+00:00</updated><id>https://sgeos.github.io/rust/no_std/2022/12/01/cargo_workspaces_for_rust</id><content type="html" xml:base="https://sgeos.github.io/rust/no_std/2022/12/01/cargo_workspaces_for_rust.html"><![CDATA[<!-- A61 -->
<script>console.log("A61");</script>

<p>A single Rust package can contain one library and multiple binaries.
Rust dependencies can only be specified at the package level, not for each
binary in a package.
If multiple binaries require different dependencies, using a cargo workspace
is the appropriate way to organize a project.</p>

<p>There are multiple ways to organize a workspace.
This post will cover workspace organization for a no_std library that is
called from both std and no_std binaries.
The library will accept an FFI-safe pointer to a logging function.
The binaries will call the library, and log command line arguments using the
same function without passing it to the library.</p>

<h2 id="software-versions">Software Versions</h2>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nv">$ </span><span class="nb">date</span> <span class="nt">-u</span> <span class="s2">"+%Y-%m-%d %H:%M:%S +0000"</span>
2022-12-01 18:34:26 +0000
<span class="nv">$ </span><span class="nb">uname</span> <span class="nt">-vm</span>
Darwin Kernel Version 20.6.0: Thu Sep 29 20:15:11 PDT 2022<span class="p">;</span> root:xnu-7195.141.42~1/RELEASE_X86_64 x86_64
<span class="nv">$ </span>ex <span class="nt">-s</span> +<span class="s1">'%s/&lt;[^&gt;].\{-}&gt;//ge'</span> +<span class="s1">'%s/\s\+//e'</span> +<span class="s1">'%norm J'</span> +<span class="s1">'g/^$/d'</span> +%p +q! /System/Library/CoreServices/SystemVersion.plist | <span class="nb">grep</span> <span class="nt">-E</span> <span class="s1">'ProductName|ProductVersion'</span> | <span class="nb">sed</span> <span class="s1">'s/^[^ ]* //g'</span> | <span class="nb">sed</span> <span class="s1">'N; s/\n/ /g'</span>
macOS 11.7.1
<span class="nv">$ </span>sysctl <span class="nt">-n</span> machdep.cpu.brand_string
Intel<span class="o">(</span>R<span class="o">)</span> Core<span class="o">(</span>TM<span class="o">)</span> i7-4790K CPU @ 4.00GHz
<span class="nv">$ </span>cargo <span class="nt">--version</span>
cargo 1.67.0-nightly <span class="o">(</span>ba607b23d 2022-11-22<span class="o">)</span></code></pre></figure>

<h2 id="instructions">Instructions</h2>

<h4 id="workspace">Workspace</h4>

<p>First, create workspace directory and add all of the member packages for
the project.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">mkdir </span>workspace
<span class="nb">cd </span>workspace
cargo new pc
cargo new embedded
cargo new core_library <span class="nt">--lib</span></code></pre></figure>

<p>Next, add a top level <strong>Cargo.toml</strong> file that lists project level metadata
and all of member packages.</p>

<p><strong>Cargo.toml</strong></p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[workspace.package]</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">edition</span> <span class="p">=</span> <span class="s">"2021"</span>

<span class="nn">[workspace]</span>
<span class="py">members</span> <span class="p">=</span> <span class="p">[</span>
  <span class="s">"pc"</span><span class="p">,</span>
  <span class="s">"embedded"</span><span class="p">,</span>
  <span class="s">"core_library"</span><span class="p">,</span>
<span class="p">]</span></code></pre></figure>

<h4 id="core-library">Core Library</h4>

<p>Move into the <strong>core_library</strong> package.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">cd </span>core_library</code></pre></figure>

<p>Modify <strong>Cargo.toml</strong>.
Note that version and edition are inherited from the workspace.</p>

<p><strong>core_library/Cargo.toml</strong></p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"core_library"</span>
<span class="py">version.workspace</span> <span class="p">=</span> <span class="kc">true</span>
<span class="py">edition.workspace</span> <span class="p">=</span> <span class="kc">true</span>

<span class="nn">[dependencies]</span></code></pre></figure>

<p>Add the no_std library code.
It accepts an FFI-safe logging function and uses it to print a message.</p>

<p><strong>core_library/src/lib.rs</strong></p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="nd">#![no_std]</span>
<span class="k">extern</span> <span class="k">crate</span> <span class="n">alloc</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">alloc</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="n">CString</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::</span><span class="nn">ffi</span><span class="p">::</span><span class="nb">c_char</span><span class="p">;</span>

<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">run</span><span class="p">(</span><span class="k">log</span><span class="p">:</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span><span class="p">(</span><span class="o">*</span><span class="k">const</span> <span class="nb">c_char</span><span class="p">))</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">CString</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="s">"Hello, core_library!"</span><span class="p">)</span>
    <span class="nf">.expect</span><span class="p">(</span><span class="s">"CString::new failed"</span><span class="p">);</span>
  <span class="k">log</span><span class="p">(</span><span class="n">message</span><span class="nf">.as_ptr</span><span class="p">());</span>
<span class="p">}</span></code></pre></figure>

<p>It should now be possible to build but not run the library.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">cargo build</code></pre></figure>

<h4 id="std-rust-binary">std Rust Binary</h4>

<p>Move into the <strong>pc</strong> package.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">cd</span> ../pc</code></pre></figure>

<p>Modify <strong>Cargo.toml</strong>.
Note the <strong>core_library</strong> sibling dependency,
in addition to inherited metadata.</p>

<p><strong>pc/Cargo.toml</strong></p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"pc"</span>
<span class="py">version.workspace</span> <span class="p">=</span> <span class="kc">true</span>
<span class="py">edition.workspace</span> <span class="p">=</span> <span class="kc">true</span>

<span class="nn">[dependencies]</span>
<span class="nn">core_library</span> <span class="o">=</span> <span class="p">{</span> <span class="py">path</span> <span class="p">=</span> <span class="s">"../core_library"</span> <span class="p">}</span></code></pre></figure>

<p>The code for the binary follows.
Note that the <strong>core_library</strong> sibling dependency can be used like any
other dependency.
The code defines an FFI-safe logging function for the library, and an
adapter to the logging function that takes a String for use in local code.
It then logs a message, calls the library, and echoes the command line
arguments.</p>

<p><strong>pc/src/main.rs</strong></p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="k">use</span> <span class="nn">core_library</span><span class="p">::</span><span class="n">run</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">std</span><span class="p">::{</span> <span class="n">env</span><span class="p">,</span> <span class="nn">ffi</span><span class="p">::</span><span class="n">CStr</span><span class="p">,</span> <span class="nn">ffi</span><span class="p">::</span><span class="n">CString</span><span class="p">,</span> <span class="nn">os</span><span class="p">::</span><span class="nn">raw</span><span class="p">::</span><span class="nb">c_char</span><span class="p">,</span> <span class="p">};</span>

<span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="k">log</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="nb">c_char</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">cstr</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">CStr</span><span class="p">::</span><span class="nf">from_ptr</span><span class="p">(</span><span class="n">message</span><span class="p">)</span> <span class="p">};</span>
  <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from_utf8_lossy</span><span class="p">(</span><span class="n">cstr</span><span class="nf">.to_bytes</span><span class="p">())</span><span class="nf">.to_string</span><span class="p">();</span>
  <span class="nd">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span> <span class="n">output</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">local_log</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="nn">CString</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
    <span class="nf">.expect</span><span class="p">(</span><span class="s">"CString::new failed"</span><span class="p">);</span>
  <span class="k">log</span><span class="p">(</span><span class="n">output</span><span class="nf">.as_ptr</span><span class="p">());</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="s">"Hello, pc!"</span><span class="p">);</span>
  <span class="nf">local_log</span><span class="p">(</span><span class="n">message</span><span class="p">);</span>
  <span class="nf">run</span><span class="p">(</span><span class="k">log</span><span class="p">);</span>
  <span class="k">let</span> <span class="n">args</span><span class="p">:</span> <span class="nb">Vec</span><span class="o">&lt;</span><span class="nb">String</span><span class="o">&gt;</span> <span class="o">=</span> <span class="nn">env</span><span class="p">::</span><span class="nf">args</span><span class="p">()</span><span class="nf">.collect</span><span class="p">();</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="n">args</span><span class="nf">.len</span><span class="p">()</span> <span class="p">{</span>
    <span class="nf">local_log</span><span class="p">(</span><span class="nd">format!</span><span class="p">(</span><span class="s">"{}: {}"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">args</span><span class="p">[</span><span class="n">i</span><span class="p">]));</span>
  <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>Run the binary with and without arguments to make sure everything works.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">cargo run
cargo run <span class="nt">--</span> a bc def</code></pre></figure>

<h4 id="no_std-rust-binary">no_std Rust Binary</h4>

<p>Finally, move into the <strong>embedded</strong> package.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="nb">cd</span> ../embedded</code></pre></figure>

<p>Modify <strong>Cargo.toml</strong>.
This binary depends on <strong>libc_alloc</strong> because it is
a core+alloc no_std binary.</p>

<p><strong>embedded/Cargo.toml</strong></p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[package]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"embedded"</span>
<span class="py">version.workspace</span> <span class="p">=</span> <span class="kc">true</span>
<span class="py">edition.workspace</span> <span class="p">=</span> <span class="kc">true</span>

<span class="nn">[dependencies]</span>
<span class="nn">core_library</span> <span class="o">=</span> <span class="p">{</span> <span class="py">path</span> <span class="p">=</span> <span class="s">"../core_library"</span> <span class="p">}</span>
<span class="py">libc_alloc</span> <span class="p">=</span> <span class="s">"1.0.3"</span></code></pre></figure>

<p>The code for the binary does exactly the same thing as the std Rust code.
Lacking std results in more verbose code that is a little harder to follow.</p>

<p><strong>embedded/src/main.rs</strong></p>

<figure class="highlight"><pre><code class="language-rust" data-lang="rust"><span class="nd">#![no_std]</span>
<span class="nd">#![no_main]</span>
<span class="nd">#![feature(lang_items)]</span>
<span class="nd">#![feature(rustc_private)]</span>
<span class="nd">#![feature(default_alloc_error_handler)]</span>
<span class="nd">#[global_allocator]</span>
<span class="k">static</span> <span class="n">ALLOCATOR</span><span class="p">:</span> <span class="p">::</span><span class="nn">libc_alloc</span><span class="p">::</span><span class="n">LibcAlloc</span> <span class="o">=</span> <span class="p">::</span><span class="nn">libc_alloc</span><span class="p">::</span><span class="n">LibcAlloc</span><span class="p">;</span>

<span class="nd">#[macro_use]</span>
<span class="k">extern</span> <span class="k">crate</span> <span class="n">alloc</span><span class="p">;</span>
<span class="k">extern</span> <span class="k">crate</span> <span class="n">libc</span><span class="p">;</span>

<span class="k">use</span> <span class="nn">alloc</span><span class="p">::{</span> <span class="nn">ffi</span><span class="p">::</span><span class="n">CString</span><span class="p">,</span> <span class="nn">string</span><span class="p">::</span><span class="nb">String</span><span class="p">,</span> <span class="nn">string</span><span class="p">::</span><span class="n">ToString</span> <span class="p">};</span>
<span class="k">use</span> <span class="nn">core</span><span class="p">::{</span> <span class="nn">ffi</span><span class="p">::</span><span class="nb">c_char</span><span class="p">,</span> <span class="nn">ffi</span><span class="p">::</span><span class="n">CStr</span><span class="p">,</span> <span class="p">};</span>
<span class="k">use</span> <span class="nn">core_library</span><span class="p">::</span><span class="n">run</span><span class="p">;</span>
<span class="k">use</span> <span class="nn">libc</span><span class="p">::</span><span class="nb">c_int</span><span class="p">;</span>

<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="k">log</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="nb">c_char</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">format</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"%s</span><span class="se">\n\0</span><span class="s">"</span><span class="p">);</span>
  <span class="k">unsafe</span> <span class="p">{</span>
    <span class="nn">libc</span><span class="p">::</span><span class="nf">printf</span><span class="p">(</span>
      <span class="n">format</span><span class="nf">.as_ptr</span><span class="p">()</span> <span class="k">as</span> <span class="o">*</span><span class="k">const</span> <span class="n">_</span><span class="p">,</span>
      <span class="n">message</span><span class="p">,</span>
    <span class="p">);</span>
  <span class="p">}</span>
<span class="p">}</span>

<span class="k">fn</span> <span class="nf">local_log</span><span class="p">(</span><span class="n">message</span><span class="p">:</span> <span class="nb">String</span><span class="p">)</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="nn">CString</span><span class="p">::</span><span class="nf">new</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
    <span class="nf">.expect</span><span class="p">(</span><span class="s">"CString::new failed"</span><span class="p">);</span>
  <span class="k">log</span><span class="p">(</span><span class="n">output</span><span class="nf">.as_ptr</span><span class="p">());</span>
<span class="p">}</span>

<span class="nd">#[no_mangle]</span>
<span class="k">pub</span> <span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">main</span><span class="p">(</span><span class="n">argc</span><span class="p">:</span> <span class="nb">c_int</span><span class="p">,</span> <span class="n">argv</span><span class="p">:</span> <span class="o">*</span><span class="k">const</span> <span class="o">*</span><span class="k">const</span> <span class="nb">c_char</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="nb">c_int</span> <span class="p">{</span>
  <span class="k">let</span> <span class="n">message</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from</span><span class="p">(</span><span class="s">"Hello, embedded!"</span><span class="p">);</span>
  <span class="nf">local_log</span><span class="p">(</span><span class="n">message</span><span class="p">);</span>
  <span class="nf">run</span><span class="p">(</span><span class="k">log</span><span class="p">);</span>
  <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">..</span><span class="n">argc</span> <span class="p">{</span>
    <span class="k">let</span> <span class="n">cstr</span> <span class="o">=</span> <span class="k">unsafe</span> <span class="p">{</span> <span class="nn">CStr</span><span class="p">::</span><span class="nf">from_ptr</span><span class="p">(</span><span class="o">*</span><span class="n">argv</span><span class="nf">.offset</span><span class="p">(</span><span class="n">i</span> <span class="k">as</span> <span class="nb">isize</span><span class="p">))</span> <span class="p">};</span>
    <span class="k">let</span> <span class="n">safe_string</span> <span class="o">=</span> <span class="nn">String</span><span class="p">::</span><span class="nf">from_utf8_lossy</span><span class="p">(</span><span class="n">cstr</span><span class="nf">.to_bytes</span><span class="p">())</span><span class="nf">.to_string</span><span class="p">();</span>
    <span class="k">let</span> <span class="n">output</span> <span class="o">=</span> <span class="nd">format!</span><span class="p">(</span><span class="s">"{}: {}"</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">safe_string</span><span class="p">);</span>
    <span class="nf">local_log</span><span class="p">(</span><span class="n">output</span><span class="p">);</span>
  <span class="p">}</span>
  <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="nd">#[panic_handler]</span>
<span class="k">fn</span> <span class="nf">panic</span><span class="p">(</span><span class="n">_</span><span class="p">:</span> <span class="o">&amp;</span><span class="nn">core</span><span class="p">::</span><span class="nn">panic</span><span class="p">::</span><span class="n">PanicInfo</span><span class="p">)</span> <span class="k">-&gt;</span> <span class="o">!</span> <span class="p">{</span>
  <span class="k">loop</span> <span class="p">{}</span>
<span class="p">}</span>

<span class="nd">#[lang</span> <span class="nd">=</span> <span class="s">"eh_personality"</span><span class="nd">]</span>
<span class="k">extern</span> <span class="s">"C"</span> <span class="k">fn</span> <span class="nf">eh_personality</span><span class="p">()</span> <span class="p">{}</span></code></pre></figure>

<p>It should run just like the std version, with slightly different output.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh">cargo run
cargo run <span class="nt">--</span> a bc def</code></pre></figure>

<h4 id="specifying-targets-in-a-workspace">Specifying Targets in a Workspace</h4>

<p>Commands like <strong>cargo build</strong> will operate on the curent package, or on all
package if in the top level of the workspace.
The <strong>–workspace</strong> command-line flag can be used to build all targets from
anywhere in the workspace, and the <strong>-p</strong> or <strong>–package</strong> flag can be used
to specify a particular package.</p>

<figure class="highlight"><pre><code class="language-sh" data-lang="sh"><span class="c"># return to the top level of the workspace</span>
<span class="nb">cd</span> ..

<span class="c"># this is where built targets live</span>
<span class="nb">ls </span>target/debug/

<span class="c"># remove all built targets to verify that they are rebuilt</span>
cargo clean

<span class="c"># run the following commands from anywhere in the workspace</span>
cargo build <span class="nt">--workspace</span>
cargo build <span class="nt">-p</span> core_library
cargo run <span class="nt">--package</span> pc
cargo run <span class="nt">--package</span> pc <span class="nt">--</span> one two three
cargo run <span class="nt">-p</span> embedded
cargo run <span class="nt">-p</span> embedded <span class="nt">--</span> one two three</code></pre></figure>

<h4 id="more-information">More Information</h4>

<p>The <a href="https://doc.rust-lang.org/cargo/reference/workspaces.html">Cargo</a>
and <a href="https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html">Rust Programming Language</a>
books have sections on workspaces.
The <a href="https://doc.rust-lang.org/std/ffi/index.html">documentation for std::ffi</a>
can be useful for FFI work involving strings.
Note that the types are reexported to <strong>std</strong>, but they need to be pulled
in from <strong>core</strong> or <strong>alloc</strong> in no_std Rust.
The <a href="https://docs.rust-embedded.org/book/intro/no-std.html">Embedded Rust Book</a> is a good place to get started with
no_std Rust.</p>

<h2 id="references">References:</h2>

<ul>
  <li><a href="https://doc.rust-lang.org/cargo/reference/workspaces.html">Rust, Cargo Book, 3.3 Workspaces</a></li>
  <li><a href="https://docs.rust-embedded.org/book/intro/no-std.html">Rust, Embedded Rust Book</a></li>
  <li><a href="https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html">Rust, Rust Programming Language, 14.3 Cargo Workspaces</a></li>
  <li><a href="https://doc.rust-lang.org/std/ffi/index.html">Rust, std::ffi Documentation</a></li>
</ul>]]></content><author><name>Brendan Sechter</name></author><category term="rust" /><category term="no_std" /></entry></feed>