Explored a new Programming Language in Pure Python — Lexer, Pratt Parser, Bytecode Compiler, Stack VM A developer built lumen-lang, a small programming language implemented entirely in pure Python with no dependencies or parser generators. The project includes a hand-written lexer with line and column tracking, a Pratt parser using recursive descent with precedence-climbing methods, a bytecode compiler, and a stack-based virtual machine supporting closures via upvalue capture. The author says the disassembler is one of the most useful parts of the project for tracing VM behavior back to the compiler stage. I Built a Programming Language in Pure Python — Lexer, Pratt Parser, Bytecode Compiler, Stack VM A few months ago I decided to tickle at making a programming language, have AI's help... implementation and English translation. The result is lumen-lang — 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. Here's what's inside and why I made the decisions I did. Source text → Lexer → Token stream → Pratt Parser → AST → Compiler → Bytecode → VM → Output 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. 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. The part people skip: good error messages at the lexer level save enormous debugging pain later. A bare unexpected character with no position is useless. line 12, col 7 unexpected character: '@' is not. The parser uses recursive descent with explicit precedence-climbing methods . Each method handles one precedence level and calls the next: equality → comparison → term → factor → unary → primary 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. It's also readable. You can look at term and immediately see that it handles + and - . No indirection. 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. The compiler tree-walks the AST and emits a compact instruction set into a Chunk — a flat array of opcodes and operands. Constants live in a constant pool indexed by operand. You can inspect the compiled output for any program: lumen --disassemble examples/demo.lm Output for a counter closure: == make counter == 0000 GET LOCAL 1 0001 CLOSURE 0