<?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: Michael Rivera</title>
    <description>The latest articles on DEV Community by Michael Rivera (@bleedingcodes).</description>
    <link>https://dev.to/bleedingcodes</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%2F4128673%2Fda0638ec-6423-4643-867d-24fcd3ec534f.jpg</url>
      <title>DEV Community: Michael Rivera</title>
      <link>https://dev.to/bleedingcodes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bleedingcodes"/>
    <language>en</language>
    <item>
      <title>Broken SFTP file Transfers Enraged me to Build a Proper Transfer Engine</title>
      <dc:creator>Michael Rivera</dc:creator>
      <pubDate>Thu, 17 Sep 2026 13:36:44 +0000</pubDate>
      <link>https://dev.to/bleedingcodes/i-got-tired-of-broken-sftp-scripts-so-i-built-a-proper-transfer-engine-1keh</link>
      <guid>https://dev.to/bleedingcodes/i-got-tired-of-broken-sftp-scripts-so-i-built-a-proper-transfer-engine-1keh</guid>
      <description>&lt;h1&gt;
  
  
  I Got Tired of Broken SFTP Scripts So I Built a Proper Transfer Engine
&lt;/h1&gt;

&lt;p&gt;Every few months I'd find a half-transferred file, a corrupted download that looked complete, or a transfer that silently failed and left nothing in the log. The scripts people write for SFTP — including mine — are usually fine until they're not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sftp-ultra&lt;/strong&gt; is what I built to replace them. A production SFTP transfer engine with concurrent workers, resumable downloads, SHA-256 verification, a SQLite transfer journal, and deterministic exit codes.&lt;/p&gt;

&lt;p&gt;Here's what it actually does and why each piece is there.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Problem With Ad-Hoc SFTP Scripts
&lt;/h2&gt;

&lt;p&gt;One-off SFTP scripts typically fail in the same ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Silent failures&lt;/strong&gt; — the transfer errors out, nothing is logged, the script exits 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrupt partial downloads&lt;/strong&gt; — the connection drops mid-file, the partial file looks like a complete file&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt; — you can't tell what transferred, when, or whether the file changed since&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No retry logic&lt;/strong&gt; — one network hiccup and the whole run fails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;sftp-ultra addresses all of these explicitly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resumable Downloads — The &lt;code&gt;.part&lt;/code&gt; Pattern
&lt;/h2&gt;

&lt;p&gt;Every download lands as &lt;code&gt;filename.part&lt;/code&gt; first. The file is only renamed to its final name after the transfer completes and (optionally) passes checksum verification. If the connection drops mid-transfer, you get a &lt;code&gt;.part&lt;/code&gt; file — not a corrupt complete file that looks valid.&lt;/p&gt;

&lt;p&gt;On the next run with &lt;code&gt;--resume&lt;/code&gt;, the engine detects existing &lt;code&gt;.part&lt;/code&gt; files, checks how much was already downloaded, and picks up from there. This matters for large files over unreliable connections.&lt;/p&gt;




&lt;h2&gt;
  
  
  SHA-256 Verification
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sftp-ultra pull &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; 192.168.1.105 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--username&lt;/span&gt; side &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--remote-root&lt;/span&gt; /home/side &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--destination&lt;/span&gt; /media/drive/downloads &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pattern&lt;/span&gt; &lt;span class="s2"&gt;"*.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checksum&lt;/span&gt; sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With &lt;code&gt;--checksum sha256&lt;/code&gt;, the engine computes the SHA-256 of the remote file and the local file after transfer and compares them. Mismatch = transfer failed, file is removed, logged as failed, does not silently sit in your destination directory.&lt;/p&gt;

&lt;p&gt;Without &lt;code&gt;--checksum&lt;/code&gt;, size and mtime are still checked before and after download — if the remote file changed mid-transfer, the download is aborted.&lt;/p&gt;




&lt;h2&gt;
  
  
  The SQLite Transfer Journal
&lt;/h2&gt;

&lt;p&gt;Every run appends to a SQLite journal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;What It Contains&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;remote_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full remote path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;local_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local destination path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;status&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;copied&lt;/code&gt;, &lt;code&gt;skipped&lt;/code&gt;, &lt;code&gt;failed&lt;/code&gt;, &lt;code&gt;planned&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;checksum&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;SHA-256 hex digest (if enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;message&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Error message on failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;updated_at&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Query it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 .sftp-ultra.sqlite3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"SELECT remote_path, status, checksum FROM transfer_journal;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dry-run mode (&lt;code&gt;--dry-run&lt;/code&gt;) also writes to the journal with &lt;code&gt;planned&lt;/code&gt; status — so you can inspect what would have transferred before committing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Concurrent Workers
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sftp-ultra pull &lt;span class="nt"&gt;--workers&lt;/span&gt; 4  &lt;span class="c"&gt;# default&lt;/span&gt;
sftp-ultra pull &lt;span class="nt"&gt;--workers&lt;/span&gt; 16 &lt;span class="c"&gt;# faster on high-bandwidth links&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configurable worker pool up to 32. Each worker maintains its own SSH connection. On a gigabit LAN with large files, 8–16 workers makes a real difference.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deterministic Exit Codes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0   — all transfers succeeded
1   — one or more transfers failed
2   — configuration error (bad flags, missing required args)
130 — interrupted (Ctrl-C)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is what makes sftp-ultra scriptable in a pipeline. You can &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; it, check &lt;code&gt;$?&lt;/code&gt;, or wrap it in a retry loop and know exactly what happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/BleedingCodes/sftp-ultra.git
&lt;span class="nb"&gt;cd &lt;/span&gt;sftp-ultra/sftp-ultra
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

sftp-ultra pull &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; 192.168.1.105 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--username&lt;/span&gt; side &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--remote-root&lt;/span&gt; /home/side &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--destination&lt;/span&gt; ./downloads &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pattern&lt;/span&gt; &lt;span class="s2"&gt;"*.mp4"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--workers&lt;/span&gt; 4 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resume&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--checksum&lt;/span&gt; sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python 3.11+, Linux. Requires &lt;code&gt;paramiko&lt;/code&gt;. MIT license.&lt;/p&gt;




&lt;h2&gt;
  
  
  What It Doesn't Do
&lt;/h2&gt;

&lt;p&gt;Key-based auth is not currently supported — it always prompts for a password. That's a real limitation if you need fully unattended operation without a password. It's on the list.&lt;/p&gt;




&lt;h2&gt;
  
  
  When to Use It
&lt;/h2&gt;

&lt;p&gt;If you're moving files between machines on a schedule, running lab data collection off a remote node, or just tired of checking whether last night's transfer actually finished — this is the tool.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/BleedingCodes/sftp-ultra" rel="noopener noreferrer"&gt;github.com/BleedingCodes/sftp-ultra&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by MainbyteLabs — Python tooling for electronics labs, hardware shops, and Linux-based tech teams.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://github.com/MR-MainbyteLabs" rel="noopener noreferrer"&gt;github.com/MR-MainbyteLabs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>devops</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>CPU path tracer in Pure Python — No GPU, No Dependencies, Physically Based</title>
      <dc:creator>Michael Rivera</dc:creator>
      <pubDate>Thu, 17 Sep 2026 13:32:01 +0000</pubDate>
      <link>https://dev.to/bleedingcodes/i-wrote-a-path-tracer-in-pure-python-no-gpu-no-dependencies-physically-based-4947</link>
      <guid>https://dev.to/bleedingcodes/i-wrote-a-path-tracer-in-pure-python-no-gpu-no-dependencies-physically-based-4947</guid>
      <description>&lt;h1&gt;
  
  
  I Wrote a Path Tracer in Pure Python — No GPU, No Dependencies, Physically Based
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;PureTrace&lt;/strong&gt; is a CPU path tracer written entirely in Python's standard library. No NumPy, no Pillow, no native extensions, no GPU. Everything — geometry, BVH construction, PBR materials, PNG encoding, OpenEXR encoding, multiprocessing, and checkpointing — is standard library.&lt;/p&gt;

&lt;p&gt;This post is about the engineering decisions behind it and what it was like to build a production-quality rendering pipeline under that constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the Constraint?
&lt;/h2&gt;

&lt;p&gt;"No dependencies" sounds like a gimmick. It's not.&lt;/p&gt;

&lt;p&gt;The real goal was to understand what rendering pipelines actually do — not what they look like through a library's API. When you can't import NumPy, you write your own vector math. When you can't use Pillow, you write your own PNG encoder. You end up understanding every piece you would otherwise have taken for granted.&lt;/p&gt;

&lt;p&gt;The secondary benefit: zero install friction. Clone and render. No &lt;code&gt;pip install&lt;/code&gt; chain, no native extension compilation, no version conflicts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Rendering Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scene primitives → SAH BVH → Camera samples → Path tracing integrator → MIS → Tile workers → Merge → PNG / OpenEXR
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Monte Carlo path tracing with multiple-importance sampling (MIS).&lt;/strong&gt; Each pixel fires rays into the scene, bouncing off surfaces until they hit a light or exceed max depth. MIS combines direct light sampling and BSDF sampling using the power heuristic — this is what makes glass and metal converge without the firefly artifacts you get from naive path tracing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Physically based materials (GGX).&lt;/strong&gt; Diffuse, metallic, and dielectric glass — all using the GGX microfacet distribution. Roughness controls the shape of specular highlights and reflections in a physically correct way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volumetric rendering.&lt;/strong&gt; Homogeneous participating medium with free-flight sampling. Fog, anisotropic scattering, visible light transport.&lt;/p&gt;




&lt;h2&gt;
  
  
  The BVH
&lt;/h2&gt;

&lt;p&gt;Ray-scene intersection is the performance bottleneck in any path tracer. Without a spatial acceleration structure, every ray tests every primitive — O(n) per ray, which makes complex scenes completely intractable.&lt;/p&gt;

&lt;p&gt;PureTrace uses a &lt;strong&gt;binned surface-area-heuristic BVH&lt;/strong&gt; (bounding volume hierarchy). The SAH is a cost model that estimates how expensive a split will be based on the surface areas of the resulting child nodes. Binning approximates the optimal split point efficiently rather than testing every possible position.&lt;/p&gt;

&lt;p&gt;The result: ray traversal is O(log n) in the average case. A scene with thousands of triangles becomes tractable.&lt;/p&gt;

&lt;p&gt;Writing the BVH in pure Python — no SIMD, no Cython — is where the constraint hurts most. It's real. PureTrace is not fast. Use all your cores, use sensible image sizes, and use progressive sampling. But it's correct and it's readable.&lt;/p&gt;




&lt;h2&gt;
  
  
  Multiprocessing and Checkpointing
&lt;/h2&gt;

&lt;p&gt;The render is split into tiles. Each tile runs in a worker process (Python's &lt;code&gt;multiprocessing&lt;/code&gt;). The main process merges tiles as they complete and writes progressive output so you can see the image forming.&lt;/p&gt;

&lt;p&gt;Every render writes an atomic &lt;code&gt;.ptrchk&lt;/code&gt; checkpoint file. Interrupt mid-render with Ctrl-C and the completed tiles are saved. Resume with the same settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start&lt;/span&gt;
puretrace render glass-chess &lt;span class="nt"&gt;-W&lt;/span&gt; 900 &lt;span class="nt"&gt;-H&lt;/span&gt; 675 &lt;span class="nt"&gt;-s&lt;/span&gt; 1200 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;--samples-per-pass&lt;/span&gt; 4 &lt;span class="nt"&gt;-o&lt;/span&gt; chess.png

&lt;span class="c"&gt;# Interrupt, resume later&lt;/span&gt;
puretrace render glass-chess &lt;span class="nt"&gt;-W&lt;/span&gt; 900 &lt;span class="nt"&gt;-H&lt;/span&gt; 675 &lt;span class="nt"&gt;-s&lt;/span&gt; 1200 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;--samples-per-pass&lt;/span&gt; 4 &lt;span class="nt"&gt;-o&lt;/span&gt; chess.png &lt;span class="nt"&gt;--resume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Glass caustics at 1200 samples per pixel takes a long time on a CPU. Checkpointing makes that practical.&lt;/p&gt;




&lt;h2&gt;
  
  
  Output Formats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PNG&lt;/strong&gt; — 8-bit sRGB with ACES, Reinhard, or linear tone mapping. The encoder is hand-written: deflate compression via &lt;code&gt;zlib&lt;/code&gt; (stdlib), PNG chunk structure by hand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OpenEXR&lt;/strong&gt; — uncompressed scanline RGB in linear scene space, half-float. Also hand-written. No &lt;code&gt;openexr&lt;/code&gt; package.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Writing a PNG encoder is one of those things you do once and never forget. The format is simple enough to implement from the spec in an afternoon.&lt;/p&gt;




&lt;h2&gt;
  
  
  Built-in Scenes
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;puretrace render cornell &lt;span class="nt"&gt;-W&lt;/span&gt; 512 &lt;span class="nt"&gt;-H&lt;/span&gt; 512 &lt;span class="nt"&gt;-s&lt;/span&gt; 256 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;-o&lt;/span&gt; cornell.png
puretrace render spheres &lt;span class="nt"&gt;-W&lt;/span&gt; 640 &lt;span class="nt"&gt;-H&lt;/span&gt; 400 &lt;span class="nt"&gt;-s&lt;/span&gt; 128 &lt;span class="nt"&gt;-o&lt;/span&gt; spheres.png
puretrace render glass-chess &lt;span class="nt"&gt;-W&lt;/span&gt; 900 &lt;span class="nt"&gt;-H&lt;/span&gt; 675 &lt;span class="nt"&gt;-s&lt;/span&gt; 1200 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;-o&lt;/span&gt; chess.png
puretrace render fog &lt;span class="nt"&gt;-W&lt;/span&gt; 800 &lt;span class="nt"&gt;-H&lt;/span&gt; 600 &lt;span class="nt"&gt;-s&lt;/span&gt; 512 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;-o&lt;/span&gt; fog.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scene&lt;/th&gt;
&lt;th&gt;What It Tests&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cornell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Diffuse walls, rough metal, glass, soft ceiling light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spheres&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Chrome, copper, glass, depth of field, motion blur&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;glass-chess&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Lathed glass pieces on reflective checkerboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;caustics&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Glass and polished metal under compact area light&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fog&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Anisotropic participating media, visible light transport&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A first render worth looking at is 256 samples per pixel. Glass caustics converge slowly — 1000+ is normal.&lt;/p&gt;




&lt;h2&gt;
  
  
  JSON Scene Description
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"camera"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"look_from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"look_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"vertical_fov"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"aperture"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"materials"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"glass"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"base_color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.95&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"roughness"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.01&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"transmission"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"ior"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.52&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"objects"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"obj"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"room.obj"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"scale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;OBJ/MTL import, HDR environment lighting, all material parameters exposed. Enough to build real scenes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/BleedingCodes/PureTrace.git
&lt;span class="nb"&gt;cd &lt;/span&gt;PureTrace
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
puretrace render cornell &lt;span class="nt"&gt;-W&lt;/span&gt; 512 &lt;span class="nt"&gt;-H&lt;/span&gt; 512 &lt;span class="nt"&gt;-s&lt;/span&gt; 256 &lt;span class="nt"&gt;-j&lt;/span&gt; 8 &lt;span class="nt"&gt;-o&lt;/span&gt; cornell.png
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python 3.11+. Zero pip dependencies. MIT license.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Project Is For
&lt;/h2&gt;

&lt;p&gt;PureTrace is a &lt;strong&gt;deliberately readable renderer&lt;/strong&gt;. Every algorithm is in plain Python, every decision is traceable to the source. If you want to understand how path tracers work — how MIS reduces noise, how BVH traversal works, how closures get closed upvalues — this is a codebase you can actually read.&lt;/p&gt;

&lt;p&gt;It is not fast. It is correct, deterministic, and hackable.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/BleedingCodes/PureTrace" rel="noopener noreferrer"&gt;github.com/BleedingCodes/PureTrace&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by MainbyteLabs — Python tooling for electronics labs, hardware shops, and Linux-based tech teams.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/MR-MainbyteLabs" rel="noopener noreferrer"&gt;github.com/MR-MainbyteLabs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>performance</category>
      <category>python</category>
      <category>software</category>
    </item>
    <item>
      <title>Explored a new Programming Language in Pure Python — Lexer, Pratt Parser, Bytecode Compiler, Stack VM</title>
      <dc:creator>Michael Rivera</dc:creator>
      <pubDate>Thu, 17 Sep 2026 13:29:17 +0000</pubDate>
      <link>https://dev.to/bleedingcodes/i-built-a-programming-language-in-pure-python-lexer-pratt-parser-bytecode-compiler-stack-vm-26fi</link>
      <guid>https://dev.to/bleedingcodes/i-built-a-programming-language-in-pure-python-lexer-pratt-parser-bytecode-compiler-stack-vm-26fi</guid>
      <description>&lt;p&gt;I Built a Programming Language in Pure Python — Lexer, Pratt Parser, Bytecode Compiler, Stack VM&lt;/p&gt;

&lt;p&gt;A few months ago I decided to tickle at making a programming language, have AI's help... implementation and English translation.&lt;/p&gt;

&lt;p&gt;The result is &lt;strong&gt;lumen-lang&lt;/strong&gt; — a small sorta complete programming language in Pure Python, no dependencies, no parser generators, no shortcuts. It has its own syntax, compiles to bytecode, and runs on a stack-based virtual machine I wrote from scratch.&lt;/p&gt;

&lt;p&gt;Here's what's inside and why I made the decisions I did.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Source text → Lexer → Token stream → Pratt Parser → AST → Compiler → Bytecode → VM → Output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every stage is hand-written. That was deliberate — the goal was to understand what's actually happening at each step, not to produce something in the least amount of code.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lexer
&lt;/h2&gt;

&lt;p&gt;The scanner is hand-written with line and column tracking. Nothing exotic — you walk the source character by character, emit tokens, handle string escapes and number literals, and report clean errors with position info.&lt;/p&gt;

&lt;p&gt;The part people skip: good error messages at the lexer level save enormous debugging pain later. A bare &lt;code&gt;unexpected character&lt;/code&gt; with no position is useless. &lt;code&gt;[line 12, col 7] unexpected character: '@'&lt;/code&gt; is not.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Parser — Why Pratt?
&lt;/h2&gt;

&lt;p&gt;The parser uses &lt;strong&gt;recursive descent with explicit precedence-climbing methods&lt;/strong&gt;. Each method handles one precedence level and calls the next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;_equality&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;_comparison&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;_term&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;_factor&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;_unary&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;_primary&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is sometimes called a Pratt parser (or top-down operator precedence parsing). The key advantage: adding a new operator or precedence level means adding one method and wiring it into the chain. It doesn't require touching a grammar table or regenerating anything.&lt;/p&gt;

&lt;p&gt;It's also readable. You can look at &lt;code&gt;_term&lt;/code&gt; and immediately see that it handles &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt;. No indirection.&lt;/p&gt;




&lt;h2&gt;
  
  
  The AST
&lt;/h2&gt;

&lt;p&gt;Typed node hierarchy covering all language constructs. Every node is a dataclass — no dict-based AST, no stringly-typed anything. The compiler walks the AST with a visitor pattern and emits instructions for each node type.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bytecode Compiler
&lt;/h2&gt;

&lt;p&gt;The compiler tree-walks the AST and emits a compact instruction set into a &lt;code&gt;Chunk&lt;/code&gt; — a flat array of opcodes and operands. Constants live in a constant pool indexed by operand.&lt;/p&gt;

&lt;p&gt;You can inspect the compiled output for any program:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;lumen &lt;span class="nt"&gt;--disassemble&lt;/span&gt; examples/demo.lm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output for a counter closure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nasm"&gt;&lt;code&gt;&lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;make_counter&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt;
&lt;span class="err"&gt;0000&lt;/span&gt; &lt;span class="nf"&gt;GET_LOCAL&lt;/span&gt;          &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="err"&gt;0001&lt;/span&gt; &lt;span class="nf"&gt;CLOSURE&lt;/span&gt;            &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;fn&lt;/span&gt; &lt;span class="nv"&gt;next&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="err"&gt;0002&lt;/span&gt; &lt;span class="nf"&gt;GET_LOCAL&lt;/span&gt;          &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="err"&gt;0003&lt;/span&gt; &lt;span class="nf"&gt;RETURN&lt;/span&gt;

&lt;span class="err"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt; &lt;span class="err"&gt;==&lt;/span&gt;
&lt;span class="err"&gt;0000&lt;/span&gt; &lt;span class="nf"&gt;GET_UPVALUE&lt;/span&gt;        &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;0001&lt;/span&gt; &lt;span class="nf"&gt;CONSTANT&lt;/span&gt;           &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;0002&lt;/span&gt; &lt;span class="nf"&gt;ADD&lt;/span&gt;
&lt;span class="err"&gt;0003&lt;/span&gt; &lt;span class="nf"&gt;SET_UPVALUE&lt;/span&gt;        &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;0004&lt;/span&gt; &lt;span class="nf"&gt;POP&lt;/span&gt;
&lt;span class="err"&gt;0005&lt;/span&gt; &lt;span class="nf"&gt;GET_UPVALUE&lt;/span&gt;        &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="err"&gt;0006&lt;/span&gt; &lt;span class="nf"&gt;RETURN&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The disassembler is one of the most useful parts of this project. When something in the VM behaves wrong, you can look at exactly what the compiler emitted and trace the problem to the right stage.&lt;/p&gt;




&lt;h2&gt;
  
  
  The VM — Closures and Upvalues
&lt;/h2&gt;

&lt;p&gt;The VM is stack-based. Each function call pushes a call frame with its own stack window and instruction pointer.&lt;/p&gt;

&lt;p&gt;The interesting part is &lt;strong&gt;upvalue capture&lt;/strong&gt; — how closures capture variables from enclosing scopes.&lt;/p&gt;

&lt;p&gt;When the compiler sees a function reference a variable from an outer scope, it emits &lt;code&gt;GET_UPVALUE&lt;/code&gt; / &lt;code&gt;SET_UPVALUE&lt;/code&gt; instructions instead of &lt;code&gt;GET_LOCAL&lt;/code&gt;. The VM maintains an upvalue list per closure object. While the enclosing function is still on the stack, upvalues point directly into the stack. When the enclosing function returns, open upvalues are "closed" — their value is copied out of the stack into the upvalue object itself.&lt;/p&gt;

&lt;p&gt;This is the mechanism that makes this work correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fn make_counter(start) {
    let value = start;
    fn next() {
        value = value + 1;
        return value;
    }
    return next;
}

let counter = make_counter(10);
print(counter());   // 11
print(counter());   // 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;next&lt;/code&gt; captures &lt;code&gt;value&lt;/code&gt; from &lt;code&gt;make_counter&lt;/code&gt;'s scope. After &lt;code&gt;make_counter&lt;/code&gt; returns, &lt;code&gt;value&lt;/code&gt; no longer exists on the stack — but &lt;code&gt;counter&lt;/code&gt; still has a valid reference to it through the closed upvalue.&lt;/p&gt;

&lt;p&gt;Getting this right took the most time of any part of the project.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the Language Looks Like
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Fibonacci
fn fib(n) {
    if (n &amp;lt; 2) { return n; }
    return fib(n - 1) + fib(n - 2);
}
print(fib(10));   // 55

// Lists and dictionaries
let primes = [2, 3, 5, 7, 11];
print(primes[2]);   // 5

let person = {"name": "Ada", "field": "computing"};
print(person["name"]);   // Ada
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dynamically typed, expression-oriented, C-ish syntax. Closures, recursion, lists, dicts, builtins (&lt;code&gt;print&lt;/code&gt;, &lt;code&gt;len&lt;/code&gt;, &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;clock&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  Running It
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/BleedingCodes/lumen-lang.git
&lt;span class="nb"&gt;cd &lt;/span&gt;lumen-lang
python &lt;span class="nt"&gt;-m&lt;/span&gt; lumen                          &lt;span class="c"&gt;# REPL&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; lumen examples/demo.lm        &lt;span class="c"&gt;# Run a file&lt;/span&gt;
python &lt;span class="nt"&gt;-m&lt;/span&gt; lumen &lt;span class="nt"&gt;--disassemble&lt;/span&gt; examples/demo.lm   &lt;span class="c"&gt;# Inspect bytecode&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No dependencies. Python 3.11+. MIT license.&lt;/p&gt;




&lt;p&gt;Repo: &lt;a href="https://github.com/BleedingCodes/lumen-lang" rel="noopener noreferrer"&gt;github.com/BleedingCodes/lumen-lang&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by MainbyteLabs — Python tooling for electronics labs, hardware shops, and Linux-based tech teams.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;&lt;a href="https://github.com/MR-MainbyteLabs" rel="noopener noreferrer"&gt;github.com/MR-MainbyteLabs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>programming</category>
      <category>python</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
