<?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>I thought porting TinyExpr to Rust would be easy. Then the tests started disagreeing.</title>
      <dc:creator>Kausalya S</dc:creator>
      <pubDate>Thu, 13 Aug 2026 06:07:15 +0000</pubDate>
      <link>https://dev.to/kausalya_s_0f6addb958c160/i-thought-porting-tinyexpr-to-rust-would-be-easy-then-the-tests-started-disagreeing-g3o</link>
      <guid>https://dev.to/kausalya_s_0f6addb958c160/i-thought-porting-tinyexpr-to-rust-would-be-easy-then-the-tests-started-disagreeing-g3o</guid>
      <description>&lt;p&gt;I’m sharing this to show how I resurrected TinyExpr in Rust, proved behavioral equivalence, and learned from the edge cases that broke along the way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I ported TinyExpr, a tiny C mathematical expression parser, to Rust.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At first, the project looked deceptively simple.&lt;/p&gt;

&lt;p&gt;Tokenize an expression.&lt;/p&gt;

&lt;p&gt;Parse it.&lt;/p&gt;

&lt;p&gt;Build an AST.&lt;/p&gt;

&lt;p&gt;Evaluate it.&lt;/p&gt;

&lt;p&gt;Done.&lt;/p&gt;

&lt;p&gt;Except it wasn't.&lt;/p&gt;

&lt;p&gt;The difficult part wasn't getting:&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;to return:&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;The difficult part was answering a much more important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;How do you know a port is actually faithful to the original implementation?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question turned a small parser rewrite into a deep investigation of grammar, operator associativity, floating-point behavior, error handling, optimization, variables, combinatorics, and edge cases.&lt;/p&gt;

&lt;p&gt;This is the story of my TinyExpr → Rust port for &lt;strong&gt;Code Resurrection 2026&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The project
&lt;/h2&gt;

&lt;p&gt;The original TinyExpr is a lightweight C expression parser and evaluator.&lt;/p&gt;

&lt;p&gt;It can evaluate expressions 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
sqrt(100)
pow(2, 10)
sin(pi / 2)
x * y + 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The original implementation is small, but it packs a surprising amount of behavior into that small codebase.&lt;/p&gt;

&lt;p&gt;My goal wasn't to rewrite it line by line.&lt;/p&gt;

&lt;p&gt;I wanted to preserve its observable behavior while redesigning the implementation around Rust's type system.&lt;/p&gt;

&lt;p&gt;The architecture became:&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 Rust implementation uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enums instead of tagged integer flags&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Result&lt;/code&gt; instead of C-style error handling&lt;/li&gt;
&lt;li&gt;ownership instead of manual memory management&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;HashMap&lt;/code&gt; for variables&lt;/li&gt;
&lt;li&gt;a typed AST&lt;/li&gt;
&lt;li&gt;recursive-descent parsing&lt;/li&gt;
&lt;li&gt;constant-folding optimization&lt;/li&gt;
&lt;li&gt;zero &lt;code&gt;unsafe&lt;/code&gt; Rust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code was cleaner.&lt;/p&gt;

&lt;p&gt;But clean code isn't proof of compatibility.&lt;/p&gt;


&lt;h1&gt;
  
  
  The first mistake: assuming the math was the hard part
&lt;/h1&gt;

&lt;p&gt;My first instinct was to test obvious expressions:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1 + 2
2 * 3
sqrt(16)
sin(pi / 2)
pow(2, 10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Everything worked.&lt;/p&gt;

&lt;p&gt;That created a dangerous illusion.&lt;/p&gt;

&lt;p&gt;A parser can pass every "normal" test while still being behaviorally different from its predecessor.&lt;/p&gt;

&lt;p&gt;For a port, these are not enough:&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
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sqrt(16)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I needed to test the weird stuff.&lt;/p&gt;

&lt;p&gt;So I started treating the original TinyExpr behavior as a specification.&lt;/p&gt;


&lt;h1&gt;
  
  
  The test suite became the specification
&lt;/h1&gt;

&lt;p&gt;Instead of asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Does my Rust implementation look correct?"&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;blockquote&gt;
&lt;p&gt;"Does my Rust implementation behave like TinyExpr?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That changed how I wrote the tests.&lt;/p&gt;

&lt;p&gt;I built coverage around several categories:&lt;/p&gt;
&lt;h3&gt;
  
  
  Arithmetic
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  Precedence
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100^.5+1
sqrt 100 * 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Unary operators
&lt;/h3&gt;


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

&lt;/div&gt;

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


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sin
cos
sqrt
pow
atan2
log
ln
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Variables
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x+x+x-y
x*y^3
cos x + sin y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Sequences
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1,2
1,2,3
(1,2),3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Invalid expressions
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1+
(1
1**1
a+5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Numerical edge cases
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0/0
1/0
fac(-1)
ncr(2,4)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;And then I added optimization tests.&lt;/p&gt;

&lt;p&gt;Because parsing the right expression isn't enough.&lt;/p&gt;

&lt;p&gt;The optimizer has to preserve the same result too.&lt;/p&gt;


&lt;h1&gt;
  
  
  Then I found the first important difference
&lt;/h1&gt;

&lt;p&gt;One of the most interesting compatibility issues was floating-point constants.&lt;/p&gt;

&lt;p&gt;The original test expectations used values such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3.14159
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;while Rust's &lt;code&gt;f64::consts::PI&lt;/code&gt; evaluates to:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3.141592653589793
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Mathematically, both represent π to different precisions.&lt;/p&gt;

&lt;p&gt;But a compatibility test doesn't care about what &lt;em&gt;should&lt;/em&gt; happen mathematically.&lt;/p&gt;

&lt;p&gt;It cares about what the implementation actually produces.&lt;/p&gt;

&lt;p&gt;My first test failed with:&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;The difference was:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.000002653589793...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That forced me to revisit the testing strategy.&lt;/p&gt;

&lt;p&gt;The lesson was simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A compatibility test must distinguish between an intentional numerical difference and an actual semantic difference.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I adjusted the comparison to use a floating-point tolerance rather than requiring exact equality.&lt;/p&gt;

&lt;p&gt;That also fixed the corresponding optimization test for:&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;h1&gt;
  
  
  The six-hour problem wasn't actually six hours
&lt;/h1&gt;

&lt;p&gt;One of the biggest challenges wasn't a single compiler error.&lt;/p&gt;

&lt;p&gt;It was figuring out which behavior belonged to the original implementation and which behavior was an artifact of my Rust design.&lt;/p&gt;

&lt;p&gt;TinyExpr has some particularly interesting semantics around:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;^
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and unary operators.&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;isn't universally interpreted the same way by every expression language.&lt;/p&gt;

&lt;p&gt;This implementation follows the original/default TinyExpr behavior:&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;rather than:&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;That means operator associativity isn't just a parser implementation detail.&lt;/p&gt;

&lt;p&gt;It's part of the API.&lt;/p&gt;

&lt;p&gt;The same applies to expressions such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-2^2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&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;A "cleaner" grammar isn't necessarily a compatible grammar.&lt;/p&gt;

&lt;p&gt;That became one of the central principles of the project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;When porting software, don't silently fix semantics that users may depend on.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  The edge cases became the real specification
&lt;/h1&gt;

&lt;p&gt;Some of the most useful tests were the ones that looked ridiculous.&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;100^--.5+1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;100^---+-++---++-+-.5+1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;These aren't expressions anyone should casually write.&lt;/p&gt;

&lt;p&gt;But that's exactly why they're valuable.&lt;/p&gt;

&lt;p&gt;A normal application test tells you whether the implementation works for normal usage.&lt;/p&gt;

&lt;p&gt;A compatibility test tells you where the implementation's boundaries actually are.&lt;/p&gt;

&lt;p&gt;And those boundaries are often where ports break.&lt;/p&gt;


&lt;h1&gt;
  
  
  Scientific notation exposed a limitation
&lt;/h1&gt;

&lt;p&gt;Another interesting discovery was scientific notation.&lt;/p&gt;

&lt;p&gt;The original TinyExpr test suite contains expressions using values such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1e3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But the Rust lexer in this implementation currently recognizes numeric literals using digits and &lt;code&gt;.&lt;/code&gt; without exponent notation.&lt;/p&gt;

&lt;p&gt;So:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1e3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;doesn't become:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Instead, it gets tokenized differently and ultimately fails.&lt;/p&gt;

&lt;p&gt;Rather than hiding that difference, I made it an explicit test:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;#[test]&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;scientific_notation_is_not_supported&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;interp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1e3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;interp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"5e-5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;interp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.0e3"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_err&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;There is a huge difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I forgot to test this."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"This behavior isn't supported, and I have a test documenting it."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second is an engineering decision.&lt;/p&gt;


&lt;h1&gt;
  
  
  Then combinatorics got interesting
&lt;/h1&gt;

&lt;p&gt;TinyExpr includes:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fac()
ncr()
npr()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;for factorials, combinations, and permutations.&lt;/p&gt;

&lt;p&gt;These exposed another subtle difference.&lt;/p&gt;

&lt;p&gt;The original C implementation performs integer-style operations and has overflow behavior that occurs much earlier than an IEEE-754 &lt;code&gt;f64&lt;/code&gt; would naturally overflow.&lt;/p&gt;

&lt;p&gt;The Rust implementation uses &lt;code&gt;f64&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;So something like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ncr(300,100)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can remain finite in Rust even though the original implementation treats the corresponding integer calculation as overflowing.&lt;/p&gt;

&lt;p&gt;That meant I couldn't simply write:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="nf"&gt;.is_infinite&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;for every upstream overflow case.&lt;/p&gt;

&lt;p&gt;I had to distinguish:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;original integer overflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;from:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;actual f64 overflow
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That resulted in separate tests documenting the divergence.&lt;/p&gt;

&lt;p&gt;This is one of the places where I decided that &lt;strong&gt;being explicit about a behavioral difference was better than pretending the implementations were identical&lt;/strong&gt;.&lt;/p&gt;


&lt;h1&gt;
  
  
  Optimization had to be tested independently
&lt;/h1&gt;

&lt;p&gt;The project also includes constant folding.&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;5 + 5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can become:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


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

&lt;p&gt;Likewise:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pow(2,2)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can become:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The optimizer therefore has its own correctness requirement.&lt;/p&gt;

&lt;p&gt;I tested that a constant expression becomes an AST number directly:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;5 + 5
      ↓
   Number(10)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But I also tested something more important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Does optimized evaluation agree with normal evaluation?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Because an optimizer that produces the wrong answer faster is still wrong.&lt;/p&gt;


&lt;h1&gt;
  
  
  Variables gave me another compatibility problem
&lt;/h1&gt;

&lt;p&gt;Variables look simple:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x * y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But variable behavior is part of the language semantics.&lt;/p&gt;

&lt;p&gt;I tested expressions against changing environments:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;x = 0, y = 2
x = 1, y = 2
x = 2, y = 2
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and checked expressions such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cos x + sin y
x+x+x-y
x*y^3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I also tested unknown identifiers.&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;xx*y^3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;can parse successfully as an identifier and then fail during evaluation because &lt;code&gt;xx&lt;/code&gt; isn't bound.&lt;/p&gt;

&lt;p&gt;That's different from a parser-level syntax error.&lt;/p&gt;

&lt;p&gt;Again:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Parsing and evaluation are separate stages, and the tests need to know which stage is responsible for the failure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h1&gt;
  
  
  The test results
&lt;/h1&gt;

&lt;p&gt;After the implementation and fixes, the project reached:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;30 passed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;for the library's unit tests.&lt;/p&gt;

&lt;p&gt;The smoke/conformance suite reached:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13 passed
2 ignored
0 failed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The two ignored tests correspond to functionality that the current Rust API doesn't implement yet:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;custom user-defined functions
closures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I deliberately left those as ignored tests rather than pretending the feature exists.&lt;/p&gt;

&lt;p&gt;That's important because an ignored test isn't a failure.&lt;/p&gt;

&lt;p&gt;It's a documented boundary.&lt;/p&gt;


&lt;h1&gt;
  
  
  Why I didn't just delete the unsupported tests
&lt;/h1&gt;

&lt;p&gt;This was a deliberate decision.&lt;/p&gt;

&lt;p&gt;The original TinyExpr supports registering custom functions and closures.&lt;/p&gt;

&lt;p&gt;My Rust port currently has a closed set of built-in functions.&lt;/p&gt;

&lt;p&gt;So instead of deleting the corresponding coverage, I left explicit ignored tests explaining:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this feature exists upstream
this port doesn't implement it yet
this test should become active when the API exists
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;That gives future development a target.&lt;/p&gt;

&lt;p&gt;It also makes the limitation visible.&lt;/p&gt;


&lt;h1&gt;
  
  
  The benchmark isn't the proof
&lt;/h1&gt;

&lt;p&gt;I also added Criterion benchmarks.&lt;/p&gt;

&lt;p&gt;They measure common expression workloads such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;arithmetic&lt;/li&gt;
&lt;li&gt;nested expressions&lt;/li&gt;
&lt;li&gt;function evaluation&lt;/li&gt;
&lt;li&gt;variable lookup&lt;/li&gt;
&lt;li&gt;expression trees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's tempting to make performance the headline.&lt;/p&gt;

&lt;p&gt;But that's not what mattered most.&lt;/p&gt;

&lt;p&gt;A fast parser that changes the semantics of:&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;isn't a successful compatibility port.&lt;/p&gt;

&lt;p&gt;The priority was:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Correctness
    ↓
Behavioral compatibility
    ↓
Test coverage
    ↓
Optimization
    ↓
Performance
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not the other way around.&lt;/p&gt;


&lt;h1&gt;
  
  
  The architecture is where Rust made the biggest difference
&lt;/h1&gt;

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

&lt;p&gt;That means concepts such as:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory ownership
raw pointers
manual cleanup
error codes
tagged structures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;have to be handled explicitly.&lt;/p&gt;

&lt;p&gt;In Rust, I could redesign these around the type system.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TinyExpr C&lt;/th&gt;
&lt;th&gt;TinyExpr Rust&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;malloc/free&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Ownership + &lt;code&gt;Drop&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raw pointers&lt;/td&gt;
&lt;td&gt;References / owned values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NULL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Option&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error codes&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Result&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tagged structures&lt;/td&gt;
&lt;td&gt;Enums&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manual cleanup&lt;/td&gt;
&lt;td&gt;Automatic cleanup&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The result isn't simply "the same C code written with Rust syntax."&lt;/p&gt;

&lt;p&gt;It's a Rust implementation of the same language behavior.&lt;/p&gt;

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


&lt;h1&gt;
  
  
  What I would do differently
&lt;/h1&gt;

&lt;p&gt;If I started this project again, I would change one major thing.&lt;/p&gt;
&lt;h2&gt;
  
  
  I would build the compatibility tests first.
&lt;/h2&gt;

&lt;p&gt;My first instinct was:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Port code
↓
Make it compile
↓
Write tests
↓
Fix failures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I now think the better order is:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Study original behavior
        ↓
Build compatibility tests
        ↓
Port architecture
        ↓
Run tests
        ↓
Investigate divergences
        ↓
Optimize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Because when you're porting software, the biggest danger isn't a compiler error.&lt;/p&gt;

&lt;p&gt;It's &lt;strong&gt;an implementation that compiles, looks reasonable, and is subtly wrong.&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  What I learned from TinyExpr
&lt;/h1&gt;

&lt;p&gt;The biggest lesson wasn't about Rust.&lt;/p&gt;

&lt;p&gt;It was about software archaeology.&lt;/p&gt;

&lt;p&gt;When you resurrect an old project, you aren't simply translating code.&lt;/p&gt;

&lt;p&gt;You're reconstructing intent.&lt;/p&gt;

&lt;p&gt;You have to ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What behavior is intentional?&lt;/li&gt;
&lt;li&gt;What behavior is accidental?&lt;/li&gt;
&lt;li&gt;What behavior is part of the public API?&lt;/li&gt;
&lt;li&gt;What edge cases did the original authors implicitly support?&lt;/li&gt;
&lt;li&gt;Which differences are acceptable in the new implementation?&lt;/li&gt;
&lt;li&gt;Which differences would break compatibility?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And sometimes the answer isn't obvious from the source.&lt;/p&gt;

&lt;p&gt;The tests become historical evidence.&lt;/p&gt;

&lt;p&gt;The parser becomes a specification.&lt;/p&gt;

&lt;p&gt;The strange edge cases become documentation.&lt;/p&gt;

&lt;p&gt;And failures become clues.&lt;/p&gt;


&lt;h1&gt;
  
  
  The most important distinction: port vs rewrite
&lt;/h1&gt;

&lt;p&gt;This project taught me that there are two very different goals:&lt;/p&gt;
&lt;h3&gt;
  
  
  Rewrite
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"How would I design this library today?"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Port
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;"How do I preserve what this library already means?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Those goals can produce completely different implementations.&lt;/p&gt;

&lt;p&gt;For TinyExpr, I chose the second.&lt;/p&gt;

&lt;p&gt;I wanted the internals to be idiomatic Rust.&lt;/p&gt;

&lt;p&gt;But I wanted the language semantics to remain recognizably TinyExpr.&lt;/p&gt;

&lt;p&gt;That's why I preserved things like:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;left-associative power
unary operator behavior
sequence expressions
built-in functions
variable evaluation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;while changing the underlying architecture.&lt;/p&gt;


&lt;h1&gt;
  
  
  The impact of a tiny project
&lt;/h1&gt;

&lt;p&gt;TinyExpr isn't a massive framework.&lt;/p&gt;

&lt;p&gt;That's exactly why I liked it for Code Resurrection.&lt;/p&gt;

&lt;p&gt;It demonstrates something important:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Software doesn't have to be huge to be worth preserving.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A small parser can sit underneath calculators, configuration systems, scientific tools, games, simulations, embedded applications, and countless internal utilities.&lt;/p&gt;

&lt;p&gt;Preserving a tiny piece of software is also an exercise in preserving the assumptions built around it.&lt;/p&gt;

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

&lt;p&gt;The behavior may not be.&lt;/p&gt;


&lt;h1&gt;
  
  
  The final result
&lt;/h1&gt;

&lt;p&gt;I started with a C expression evaluator.&lt;/p&gt;

&lt;p&gt;I ended with a Rust implementation containing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a lexer&lt;/li&gt;
&lt;li&gt;recursive-descent parser&lt;/li&gt;
&lt;li&gt;typed AST&lt;/li&gt;
&lt;li&gt;evaluator&lt;/li&gt;
&lt;li&gt;optimizer&lt;/li&gt;
&lt;li&gt;built-in mathematical functions&lt;/li&gt;
&lt;li&gt;variable support&lt;/li&gt;
&lt;li&gt;structured errors&lt;/li&gt;
&lt;li&gt;compatibility tests&lt;/li&gt;
&lt;li&gt;smoke tests&lt;/li&gt;
&lt;li&gt;examples&lt;/li&gt;
&lt;li&gt;Criterion benchmarks&lt;/li&gt;
&lt;li&gt;zero &lt;code&gt;unsafe&lt;/code&gt; Rust&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And more importantly, I ended with a much better understanding of what it actually means to &lt;strong&gt;port software faithfully&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The hardest bugs weren't syntax errors.&lt;/p&gt;

&lt;p&gt;They were assumptions.&lt;/p&gt;


&lt;h1&gt;
  
  
  The lesson I'll take into my next port
&lt;/h1&gt;

&lt;p&gt;If I had to reduce the entire project to one sentence, it would be this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Don't prove that your new implementation works. Prove that it still means the same thing.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's a much harder problem.&lt;/p&gt;

&lt;p&gt;But it's also a much more interesting one.&lt;/p&gt;

&lt;p&gt;And that's what made resurrecting TinyExpr worth doing.&lt;/p&gt;


&lt;h2&gt;
  
  
  Built for Code Resurrection 2026
&lt;/h2&gt;

&lt;p&gt;This project was built as part of &lt;strong&gt;Code Resurrection 2026&lt;/strong&gt;, with the goal of taking an existing piece of software and giving it a modern implementation without losing the behavior that made the original useful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project:&lt;/strong&gt; &lt;code&gt;tinyexpr-rs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Original project:&lt;/strong&gt; TinyExpr&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Port:&lt;/strong&gt; C → Rust&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Focus:&lt;/strong&gt; behavioral compatibility, safety, maintainability, testing, and performance&lt;/p&gt;

&lt;p&gt;The project is not perfect—and that's intentional.&lt;/p&gt;

&lt;p&gt;The remaining limitations are documented rather than hidden.&lt;/p&gt;

&lt;p&gt;Because in a resurrection project, &lt;strong&gt;knowing what you haven't reproduced yet is just as important as knowing what you have.&lt;/strong&gt;&lt;/p&gt;


&lt;h3&gt;
  
  
  One final thought
&lt;/h3&gt;

&lt;p&gt;The most surprising thing about this project was how little of the work was actually typing Rust.&lt;/p&gt;

&lt;p&gt;The real work was asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"What does this tiny piece of C actually promise?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then writing enough evidence to defend the answer.&lt;/p&gt;

&lt;p&gt;That's the part of software resurrection I didn't expect to enjoy as much as I did.&lt;/p&gt;

&lt;p&gt;And it's probably the part I'll remember longest.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Kausalya-s673" rel="noopener noreferrer"&gt;
        Kausalya-s673
      &lt;/a&gt; / &lt;a href="https://github.com/Kausalya-s673/tinyexpr-rs" rel="noopener noreferrer"&gt;
        tinyexpr-rs
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Idiomatic Rust port of TinyExpr with recursive-descent parsing, AST optimization, benchmarking and full test suite.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;tinyexpr-rs&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;A safe, idiomatic Rust port of the original &lt;strong&gt;TinyExpr&lt;/strong&gt; mathematical expression parser and evaluator.&lt;/p&gt;

&lt;p&gt;Built as part of &lt;strong&gt;Code Resurrection 2026&lt;/strong&gt;, this project preserves the grammar and behavior of the original C implementation while redesigning the internals to leverage Rust's ownership model, type system, and modern error handling.&lt;/p&gt;




&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Overview&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;TinyExpr is a lightweight recursive-descent parser capable of parsing and evaluating mathematical expressions such as&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;2 + 3 * 4
sqrt(16)
pow(2, 10)
sin(pi / 2)
fac(5)
ncr(5,2)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This project reimplements TinyExpr in Rust while preserving the original parser grammar and evaluation semantics wherever practical.&lt;/p&gt;
&lt;p&gt;Unlike a direct line-by-line translation, the implementation embraces idiomatic Rust design using enums, pattern matching, ownership, and &lt;code&gt;Result&lt;/code&gt;-based error handling.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Features&lt;/h1&gt;
&lt;/div&gt;


&lt;ul&gt;

&lt;li&gt;Recursive-descent parser&lt;/li&gt;

&lt;li&gt;Expression evaluation&lt;/li&gt;

&lt;li&gt;Constant folding optimization&lt;/li&gt;

&lt;li&gt;Built-in mathematical functions&lt;/li&gt;

&lt;li&gt;Variables&lt;/li&gt;

&lt;li&gt;Built-in constants (&lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;e&lt;/code&gt;)&lt;/li&gt;

&lt;li&gt;Comprehensive unit and integration tests&lt;/li&gt;

&lt;li&gt;Smoke test suite based on the original TinyExpr tests&lt;/li&gt;

&lt;li&gt;…&lt;/li&gt;

&lt;/ul&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Kausalya-s673/tinyexpr-rs" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

</description>
      <category>hackathonraptors</category>
      <category>c</category>
      <category>rust</category>
      <category>coderesurrection</category>
    </item>
    <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>
