A binary-tree assignment to evaluate 1 + 1 + 1 led one programmer to build graphLang, a small functional-leaning language runtime in C. The author’s account traces the scope creep through a key idea—treat operators as functions—and the practical problems that followed: representing variables and functions, allocating enough expression nodes, and reclaiming memory.
Why an arithmetic tree became a language project
The author opens the project story with a data-structures exercise: convert an arithmetic expression into a binary tree. Rather than write a special evaluator for each operator, the author reframed operators as functions that take expressions. That made evaluation a matter of applying a function to its arguments, a more general mechanism than separate addition, subtraction, or multiplication cases.
As an Amazon Associate I earn from qualifying purchases.
That shift changed the problem. Once expressions could be evaluated through functions, the runtime needed a way to name values, represent functions as data, and manage the growing graph of expression nodes. The author characterizes the result as a “Graph Reduction engine.”
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →What the runtime needed beyond arithmetic
Variables and environments
Variables require a mapping from names to values. The author added an environment backed by a hash table so expressions could look up bindings during evaluation.
#1 Best Overall
User-defined functions and closures
A C function pointer alone was not enough for a function that should exist within the language’s expression graph and be returned for later evaluation. The article describes representing user-defined functions as closures: graph nodes containing function parameters and bodies. This lets the evaluator treat a function as part of the language’s data rather than only as a built-in C routine.
Why allocation became the next obstacle
The first design used a fixed arena of 1,024 nodes. In the author’s fib(5) example, that was not enough: the author reports that it spawned 13,000 nodes. Expanding one contiguous block could move it and invalidate pointers into the old allocation, so the allocator changed to linked chunks. The author reports that this version used 1.32 MB for fib(5).
The article estimates an expression node at 32 bytes on a 64-bit system before allocator overhead, and says malloc() added 16 bytes on the author’s system. Those are implementation-specific estimates, not universal C layout or allocator constants. With chunk allocation but no collection, the author reports 40 MB for fib(10).
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallHow mark-and-sweep changed the memory story
As evaluation created more nodes, allocating in chunks did not by itself reclaim nodes that were no longer needed. The author reports that fib(40) exceeded 12 GB and ended in an out-of-memory crash before garbage collection. The article estimates roughly 1.3 billion nodes and 62.4 GB of cumulative node allocations at 48 bytes per node.
The author then added tracing mark-and-sweep collection, which identifies reachable nodes and reuses unreachable ones. For fib(40), the author reports about 1.7 MB of memory use after the change, with a runtime of six minutes. These are the author’s own measurements; the article does not provide an independently replicated benchmark or enough test details to generalize the figures to other runtimes or machines.
What the public GraphLang project documents
The public GraphLang repository describes the project as a minimal, dynamically typed, functional-leaning Lisp dialect and VM. Its README documents Lisp-style expressions, variables, first-class functions, closures, let, a REPL, plugins for native functionality, and a tracing mark-and-sweep collector. It also provides make and run examples. These are claims in the project documentation, not the result of an independent code audit.
The original article mentions a lexer and parser, an FFI, a REPL, lambda functions, local variables, tail-call optimization, and a Cheney copying collector among future parts or plans. The later README describes some related capabilities, but the development story alone does not establish that each planned item was completed at the time the article was published. Neither source offers an independent review or replicated performance test.
Quick Recap
Best Value
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




