I Built a Programming Language in a Weekend. Here’s What I Learned.

a Programming Language in a Weekend. Here’s What I Learned.

**TL;DR:** I built *Arc*, a statically-typed programming language with a lexer, parser, type checker, and interpreter — all from scratch in Go. It took a weekend. Here’s what it taught me about how every programming language actually works.

## Why Would Anyone Do This?

I’ve been writing Python for 8 years. I use `if`, `for`, `def` every day. But I never really understood what happens between me typing `x = 10 + 20` and the computer knowing that `x` is `30`.

So I decided to find out — by building my own language.

## Meet Arc ⚡

Arc is a simple, statically-typed language. It looks like this:

“`
let name: string = “Dimple”
let age: int = 28

if age > 25 {
print(“experienced!”)
}

fn add(a: int, b: int) -> int {
return a + b
}

print(add(10, 20))
“`

Nothing fancy. But building it taught me more about computer science than any textbook.

## The Four Stages Every Language Goes Through

Here’s the secret: **every** programming language — Python, Go, Rust, JavaScript — does the same four things. They just do them differently.

### Stage 1: Lexing — Breaking Text into Words

When you write `let x: int = 10 + 20`, your computer doesn’t see code. It sees a string of characters:

“`
l-e-t- -x-:- -i-n-t- -=- -1-0- -+- -2-0
“`

The **lexer** is the first step. It scans this string and groups characters into meaningful chunks called *tokens*:

“`
[LET] [x] [:] [int] [=] [10] [+] [20]
“`

Each token gets a type tag. Is `x` a keyword or a variable name? Is `10` a number or a string? The lexer decides.

**The insight:** Spaces, newlines, and comments? Thrown away. They’re for humans, not computers.

### Stage 2: Parsing — Building a Tree

A flat list of tokens isn’t enough. Consider:

“`
let x: int = 10 + 20 * 3
“`

Is `x` equal to `90` or `70`? The tokens don’t tell you. But a **tree** does:

“`
=
/ \
x +
/ \
10 *
/ \
20 3
“`

Multiplication is deeper in the tree, so it runs first. `20 * 3 = 60`, then `10 + 60 = 70`.

**The tree structure *is* the order of operations.** This is called an Abstract Syntax Tree (AST), and the parser builds it using an algorithm called a *Pratt parser* — which handles operator precedence elegantly.

### Stage 3: Type Checking — The Safety Net

This is what makes Arc *statically* typed. Before any code runs, the type checker walks the tree and validates every operation:

“`
let x: int = 10
let y: string = “hello”
print(x + y) // ❌ ERROR: cannot use + with int and string
“`

In Python, this would crash at runtime — maybe after 20 minutes of execution, after writing half your data to the database. In Arc, it catches the bug in milliseconds, before anything runs.

The type checker uses a **symbol table** — essentially a dictionary that maps variable names to their types. When it sees `let x: int = 10`, it stores `{x: int}`. When it later sees `x + y`, it looks up both types and validates that `+` works with them.

**Functions create nested scopes:**

“`
┌─── Global Scope ──────────────┐
│ x → int │
│ ┌─── add() Scope ─────────┐ │
│ │ a → int │ │
│ │ b → int │ │
│ │ Can see: a, b, AND x │ │
│ └──────────────────────────┘ │
│ Cannot see: a, b │
└────────────────────────────────┘
“`

Inner scopes can see outer variables. Outer scopes can’t see inner ones. This is how *every* language handles scoping.

### Stage 4: Interpreter — Running the Tree

Finally, the interpreter walks the tree bottom-up and executes it:

1. Visit `20` → returns 20
2. Visit `3` → returns 3
3. Visit `*` → computes 20 × 3 = 60
4. Visit `10` → returns 10
5. Visit `+` → computes 10 + 60 = 70
6. Visit `=` → stores x = 70

That’s it. Solve the leaves, combine upward.

## What I Actually Learned

Building Arc in a weekend taught me things I’d never gotten from tutorials:

1. **Every language is the same machine.** Python, Go, Rust — they all lex, parse, check, and execute. The differences are in *what* they check and *how* they execute.

2. **Static typing isn’t about being strict — it’s about being fast.** Catching errors before runtime isn’t a limitation. It’s a feature that saves you at 3 AM.

3. **Trees are everywhere.** HTML is a tree. JSON is a tree. Your filesystem is a tree. Code is a tree. Once you see it, you can’t unsee it.

4. **Go is great for this.** Its simplicity forced me to think clearly. No magic, no metaprogramming — just structs, interfaces, and functions.

## Try It Yourself

Arc is open source: [**github.com/bBlazewavE/arc**](https://github.com/bBlazewavE/arc)

“`bash
git clone https://github.com/bBlazewavE/arc.git
cd arc
go build -o arc .
./arc examples/hello.arc
“`

Building a programming language sounds intimidating. It’s not. It’s four steps, each one simpler than the last. And once you build one, you’ll never look at code the same way again.

Leave a Reply

Discover more from d.log

Subscribe now to keep reading and get access to the full archive.

Continue reading