<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kausalya S</title>
    <description>The latest articles on DEV Community by Kausalya S (@kausalya_s_0f6addb958c160).</description>
    <link>https://dev.to/kausalya_s_0f6addb958c160</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4070193%2F37f88d25-ed8a-48b7-869b-0b5f94993c52.png</url>
      <title>DEV Community: Kausalya S</title>
      <link>https://dev.to/kausalya_s_0f6addb958c160</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kausalya_s_0f6addb958c160"/>
    <language>en</language>
    <item>
      <title>Resurrecting TinyExpr in Rust: What a Hackathon Taught Me About Proving a Port Works</title>
      <dc:creator>Kausalya S</dc:creator>
      <pubDate>Sun, 09 Aug 2026 19:47:00 +0000</pubDate>
      <link>https://dev.to/kausalya_s_0f6addb958c160/resurrecting-tinyexpr-in-rust-what-a-hackathon-taught-me-about-proving-a-port-works-5c3k</link>
      <guid>https://dev.to/kausalya_s_0f6addb958c160/resurrecting-tinyexpr-in-rust-what-a-hackathon-taught-me-about-proving-a-port-works-5c3k</guid>
      <description>&lt;p&gt;What if “modernizing” legacy software didn't mean rewriting it and hoping nothing breaks?&lt;/p&gt;

&lt;p&gt;What if we could take a small, battle-tested C library, rebuild it in a modern language, and prove that the new implementation still behaves like the original?&lt;/p&gt;

&lt;p&gt;That was the challenge I took on during Code Resurrection 2026.&lt;/p&gt;

&lt;p&gt;Our project: TinyExpr → tinyexpr-rs, a safe, idiomatic Rust port of the original TinyExpr mathematical expression parser and evaluator.&lt;/p&gt;

&lt;p&gt;And the surprising part?&lt;/p&gt;

&lt;p&gt;The hardest part wasn't writing the parser.&lt;/p&gt;

&lt;p&gt;It was proving that we hadn't accidentally changed what the parser meant.&lt;/p&gt;

&lt;p&gt;The Problem: Legacy Code Still Has Value&lt;/p&gt;

&lt;p&gt;TinyExpr is a lightweight mathematical expression engine.&lt;/p&gt;

&lt;p&gt;Give it an expression like:&lt;/p&gt;

&lt;p&gt;2 + 3 * 4&lt;/p&gt;

&lt;p&gt;and it evaluates it to:&lt;/p&gt;

&lt;p&gt;14&lt;/p&gt;

&lt;p&gt;It supports much more than basic arithmetic:&lt;/p&gt;

&lt;p&gt;sqrt(16)&lt;br&gt;
sin(pi / 2)&lt;br&gt;
pow(2, 10)&lt;br&gt;
x*x + y*y&lt;/p&gt;

&lt;p&gt;The original implementation is written in C.&lt;/p&gt;

&lt;p&gt;For Code Resurrection, instead of simply translating the C code line by line, I wanted to answer a more interesting question:&lt;/p&gt;

&lt;p&gt;Can we resurrect the functionality while redesigning the implementation around modern Rust principles?&lt;/p&gt;

&lt;p&gt;That meant moving from C's manual memory management and raw-pointer-oriented design toward:&lt;/p&gt;

&lt;p&gt;Rust ownership&lt;br&gt;
enums&lt;br&gt;
pattern matching&lt;br&gt;
Result-based error handling&lt;br&gt;
strongly typed AST structures&lt;br&gt;
safe evaluation&lt;br&gt;
zero unsafe Rust&lt;/p&gt;

&lt;p&gt;The goal wasn't just a rewrite.&lt;/p&gt;

&lt;p&gt;It was a behavior-preserving reimplementation.&lt;/p&gt;

&lt;p&gt;From C to Rust&lt;/p&gt;

&lt;p&gt;The architecture became a clean pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Expression
    ↓
Lexer
    ↓
Parser
    ↓
AST
    ↓
Optimizer
    ↓
Evaluator
    ↓
Result

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The lexer converts the input into tokens.&lt;/p&gt;

&lt;p&gt;The recursive-descent parser converts those tokens into an Abstract Syntax Tree.&lt;/p&gt;

&lt;p&gt;The optimizer performs constant folding.&lt;/p&gt;

&lt;p&gt;The evaluator walks the resulting AST and produces the final value.&lt;/p&gt;

&lt;p&gt;This separation made the implementation easier to reason about and test.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 + 3 * 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;initially becomes something conceptually like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
+
├── 2
└── *
    ├── 3
    └── 4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The optimizer can then reduce the constant expression to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;before evaluation.&lt;/p&gt;

&lt;p&gt;But "It Compiles" Isn't Proof&lt;/p&gt;

&lt;p&gt;This was where the project became much more interesting.&lt;/p&gt;

&lt;p&gt;A port can compile.&lt;/p&gt;

&lt;p&gt;A port can have unit tests.&lt;/p&gt;

&lt;p&gt;A port can produce the right answer for ten examples.&lt;/p&gt;

&lt;p&gt;And still be behaviorally different from the original.&lt;/p&gt;

&lt;p&gt;So instead of asking:&lt;/p&gt;

&lt;p&gt;"Does my Rust implementation work?"&lt;/p&gt;

&lt;p&gt;I asked:&lt;/p&gt;

&lt;p&gt;"Does my Rust implementation preserve the behavior that matters?"&lt;/p&gt;

&lt;p&gt;That changed how I approached testing.&lt;/p&gt;

&lt;p&gt;I built a smoke-test suite based on TinyExpr's original test suite and used it to exercise:&lt;/p&gt;

&lt;p&gt;arithmetic&lt;br&gt;
operator precedence&lt;br&gt;
associativity&lt;br&gt;
unary operators&lt;br&gt;
variables&lt;br&gt;
mathematical functions&lt;br&gt;
invalid expressions&lt;br&gt;
NaN behavior&lt;br&gt;
infinity behavior&lt;br&gt;
combinatorics&lt;br&gt;
optimization&lt;br&gt;
power associativity&lt;br&gt;
edge cases&lt;/p&gt;

&lt;p&gt;The final test run reached:&lt;/p&gt;

&lt;p&gt;30 unit tests + 13 smoke tests passing, with 2 intentionally ignored tests for unsupported custom-function/closure functionality.&lt;/p&gt;

&lt;p&gt;That distinction matters.&lt;/p&gt;

&lt;p&gt;I didn't want to hide unsupported functionality behind a green test report.&lt;/p&gt;

&lt;p&gt;Then Things Broke&lt;/p&gt;

&lt;p&gt;And this is where the real engineering happened.&lt;/p&gt;

&lt;p&gt;One of the smoke tests initially failed because of something that looked almost trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
expected 3.14159
got      3.141592653589793

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Another failed around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pi * 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Rust implementation was producing the full precision of f64.&lt;/p&gt;

&lt;p&gt;The original compatibility tests were using rounded expected values.&lt;/p&gt;

&lt;p&gt;So the problem wasn't simply:&lt;/p&gt;

&lt;p&gt;"My calculation is wrong."&lt;/p&gt;

&lt;p&gt;It was:&lt;/p&gt;

&lt;p&gt;"What exactly are we defining as behavioral equivalence?"&lt;/p&gt;

&lt;p&gt;That forced me to think carefully about floating-point comparison rather than blindly matching truncated constants.&lt;/p&gt;

&lt;p&gt;Eventually, the tests were adjusted to use appropriate numerical tolerance.&lt;/p&gt;

&lt;p&gt;That was an important lesson:&lt;/p&gt;

&lt;p&gt;Compatibility isn't always byte-for-byte equality. Sometimes it means preserving semantics within the numerical model of the target language.&lt;/p&gt;

&lt;p&gt;The Edge Cases Were the Real Challenge&lt;/p&gt;

&lt;p&gt;The most interesting differences weren't always obvious.&lt;/p&gt;

&lt;p&gt;For example, the Rust lexer doesn't currently support scientific notation in the same way:&lt;/p&gt;

&lt;p&gt;1e3&lt;/p&gt;

&lt;p&gt;doesn't become:&lt;/p&gt;

&lt;p&gt;1000&lt;/p&gt;

&lt;p&gt;in this implementation.&lt;/p&gt;

&lt;p&gt;Instead, the lexer sees the numeric portion and identifier separately.&lt;/p&gt;

&lt;p&gt;So rather than silently pretending the feature existed, the test suite explicitly documents the limitation.&lt;/p&gt;

&lt;p&gt;The same philosophy applied to custom functions and closures.&lt;/p&gt;

&lt;p&gt;The original TinyExpr supports mechanisms for registering user-defined callbacks.&lt;/p&gt;

&lt;p&gt;Our current Rust implementation uses a fixed set of built-in functions.&lt;/p&gt;

&lt;p&gt;So those tests remain explicitly ignored rather than being falsely marked as passing.&lt;/p&gt;

&lt;p&gt;That's a small testing decision, but I think it represents an important engineering principle:&lt;/p&gt;

&lt;p&gt;A trustworthy test suite should tell you what the software can do, what it cannot do, and why.&lt;/p&gt;

&lt;p&gt;One of My Biggest Lessons: Ports Are Not Translations&lt;/p&gt;

&lt;p&gt;A naive port looks like:&lt;/p&gt;

&lt;p&gt;C code&lt;br&gt;
  ↓&lt;br&gt;
Rust syntax&lt;br&gt;
  ↓&lt;br&gt;
Done&lt;/p&gt;

&lt;p&gt;Our approach was closer to:&lt;/p&gt;

&lt;p&gt;Original behavior&lt;br&gt;
       ↓&lt;br&gt;
Understand semantics&lt;br&gt;
       ↓&lt;br&gt;
Design Rust architecture&lt;br&gt;
       ↓&lt;br&gt;
Implement&lt;br&gt;
       ↓&lt;br&gt;
Compare behavior&lt;br&gt;
       ↓&lt;br&gt;
Find differences&lt;br&gt;
       ↓&lt;br&gt;
Investigate&lt;br&gt;
       ↓&lt;br&gt;
Fix or document&lt;br&gt;
       ↓&lt;br&gt;
Test again&lt;/p&gt;

&lt;p&gt;That distinction is huge.&lt;/p&gt;

&lt;p&gt;For example, the original C implementation uses concepts such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight liquid"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;embed&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="nv"&gt;C&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nv"&gt;Rust&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;malloc/free&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Ownership&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;+&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Drop&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;Raw&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;pointers&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;References&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;owned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;values&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;NULL&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;Option&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;Error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;codes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Result&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;Tagged&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;flags&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;Enums&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;Manual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;cleanup&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nv"&gt;Automatic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;memory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;management&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result isn't just C code rewritten using Rust syntax.&lt;/p&gt;

&lt;p&gt;It's a redesign around Rust's strengths.&lt;/p&gt;

&lt;p&gt;Why TinyExpr Matters&lt;/p&gt;

&lt;p&gt;At first glance, a mathematical expression parser seems small.&lt;/p&gt;

&lt;p&gt;But that's exactly why it was such a good resurrection target.&lt;/p&gt;

&lt;p&gt;TinyExpr sits at an interesting boundary:&lt;/p&gt;

&lt;p&gt;small enough to understand, but rich enough to expose real language-engineering problems.&lt;/p&gt;

&lt;p&gt;Parsing isn't just about recognizing numbers.&lt;/p&gt;

&lt;p&gt;It's about answering questions like:&lt;/p&gt;

&lt;p&gt;What binds more tightly?&lt;br&gt;
Is exponentiation left- or right-associative?&lt;br&gt;
How does unary minus interact with powers?&lt;br&gt;
What happens when an expression is malformed?&lt;br&gt;
How should variables be resolved?&lt;br&gt;
What should happen with 0 / 0?&lt;br&gt;
How should mathematical domain errors behave?&lt;br&gt;
When is constant folding safe?&lt;br&gt;
What counts as equivalent floating-point output?&lt;/p&gt;

&lt;p&gt;Those are real compiler and interpreter problems hiding inside a tiny project.&lt;/p&gt;

&lt;p&gt;The Innovation Wasn't "We Rewrote C in Rust"&lt;/p&gt;

&lt;p&gt;The innovation, for me, was the methodology.&lt;/p&gt;

&lt;p&gt;We treated legacy software as something that could be understood, tested, reconstructed, and modernized rather than simply abandoned.&lt;/p&gt;

&lt;p&gt;TinyExpr became a case study in how older software can be brought into a modern ecosystem while preserving the behavior that existing users depend on.&lt;/p&gt;

&lt;p&gt;And that idea scales far beyond expression parsing.&lt;/p&gt;

&lt;p&gt;Imagine applying the same approach to:&lt;/p&gt;

&lt;p&gt;legacy scientific libraries&lt;br&gt;
old developer tools&lt;br&gt;
embedded utilities&lt;br&gt;
abandoned command-line programs&lt;br&gt;
small infrastructure libraries&lt;br&gt;
educational software&lt;br&gt;
older algorithms that are still useful but difficult to maintain&lt;/p&gt;

&lt;p&gt;The goal isn't always to replace legacy software.&lt;/p&gt;

&lt;p&gt;Sometimes the goal is to give it a safer future.&lt;/p&gt;

&lt;p&gt;What I Would Do Differently&lt;/p&gt;

&lt;p&gt;If I could restart the project, I would invest more time in defining the compatibility contract before writing the implementation.&lt;/p&gt;

&lt;p&gt;Especially around:&lt;/p&gt;

&lt;p&gt;floating-point tolerances&lt;br&gt;
unsupported language features&lt;br&gt;
error semantics&lt;br&gt;
exact edge-case behavior&lt;br&gt;
differences between C and Rust numerical behavior&lt;/p&gt;

&lt;p&gt;That would have saved debugging time later.&lt;/p&gt;

&lt;p&gt;But the debugging itself was valuable.&lt;/p&gt;

&lt;p&gt;Because every unexpected failure forced us to understand the original behavior more deeply.&lt;/p&gt;

&lt;p&gt;What Code Resurrection Changed for Me&lt;/p&gt;

&lt;p&gt;Before this project, I might have described a successful port as:&lt;/p&gt;

&lt;p&gt;"The Rust version produces the expected output."&lt;/p&gt;

&lt;p&gt;Now I'd describe it differently:&lt;/p&gt;

&lt;p&gt;A successful port is one where you understand the original behavior well enough to explain every important difference in the new implementation.&lt;/p&gt;

&lt;p&gt;That mindset completely changed how I approached the project.&lt;/p&gt;

&lt;p&gt;We didn't just resurrect TinyExpr.&lt;/p&gt;

&lt;p&gt;We built tinyexpr-rs as a safer, more maintainable Rust implementation while using tests to challenge our assumptions about compatibility.&lt;/p&gt;

&lt;p&gt;And that's what I think makes legacy resurrection exciting.&lt;/p&gt;

&lt;p&gt;The code may be old.&lt;/p&gt;

&lt;p&gt;The ideas don't have to be.&lt;/p&gt;

&lt;p&gt;What's Next?&lt;/p&gt;

&lt;p&gt;There are still areas where tinyexpr-rs can evolve:&lt;/p&gt;

&lt;p&gt;better parser diagnostics with source spans&lt;br&gt;
more expression simplification&lt;br&gt;
user-defined functions&lt;br&gt;
additional mathematical operators&lt;br&gt;
a REPL&lt;br&gt;
WebAssembly support&lt;br&gt;
broader compatibility testing&lt;/p&gt;

&lt;p&gt;But the foundation is there.&lt;/p&gt;

&lt;p&gt;A small C expression engine has become a modern Rust project with a clear architecture, automated tests, optimization, benchmarks, and documented compatibility decisions.&lt;/p&gt;

&lt;p&gt;Final Thought&lt;/p&gt;

&lt;p&gt;The most valuable thing I built during Code Resurrection wasn't just a parser.&lt;/p&gt;

&lt;p&gt;It was a way of thinking about legacy software.&lt;/p&gt;

&lt;p&gt;Don't ask only, "Can we rewrite it?"&lt;/p&gt;

&lt;p&gt;Ask:&lt;/p&gt;

&lt;p&gt;"Can we understand it deeply enough to rebuild it, test it, and give it another life?"&lt;/p&gt;

&lt;p&gt;That's what Code Resurrection meant to me.&lt;/p&gt;

&lt;p&gt;And that's what tinyexpr-rs represents:&lt;br&gt;
old functionality, new foundations, and a future worth maintaining.``&lt;/p&gt;

</description>
      <category>coderesurrection</category>
      <category>hackathonraptors</category>
      <category>tinyexpr</category>
      <category>rust</category>
    </item>
  </channel>
</rss>
