# How Does a Text File Become a "Program"?
5040
let, the precedence of * over +, and the behavior of the while loop are all defined by us. If we can write a program that reads these rules and executes them, we own a programming language — even if ours is still small.
# Are We Building an Interpreter or a Compiler?
Reads the program's structure and executes its meaning.
Transforms the program into another representation such as machine code or IR.
# The Roadmap of Our Language
.mini file text# First, Define the Language's Contract
true and falselet and assigning new valuesif/else conditionals and while loopsprint statement;. Conditional and loop bodies go inside { }. Conditions don't require mandatory parentheses. In this version all variables live in a single global environment — meaning we have no block scope.
# Stage One — Lexer: Turn Text into Tokens
10 isn't just text — the Lexer also stores its numeric value. Likewise, let initially reads like an identifier, and then the keyword table reveals it's a reserved word.
minilang.py. To keep the project short and readable, identifiers start with an English letter or _, comments start with //, and we keep each token's line number for error messages:
= apart from == by matching the longer pattern first. Pattern order matters: if = matched first, the sequence == could be wrongly split into two separate tokens.
# Stage Two — Grammar: Which Orderings Are Valid?
| means "one of these alternatives", ? means an optional part, and * means zero or more repetitions. That * is not our language's multiplication operator — it's just part of the grammar notation.
factor reads multiplication and division operators, while term builds addition and subtraction from factors. So in the expression 2 + 3 * 4, the sub-expression 3 * 4 becomes a subtree first and is then added to 2. Operator precedence isn't the result of a hidden if — it's written directly into the grammar.
# Stage Three — AST: Keep the Program's Shape, Not the Text's
let result = 2 + 3 * 4; the conceptual tree has this hierarchy:
Node class. A large compiler usually has separate classes like BinaryExpression, VariableDeclaration, and WhileStatement; the generic model is enough for our project:
# Stage Four — Parser: Build a Tree from Tokens
statement function detects the statement type, and block collects statements until it reaches }:
10 - 3 - 2 must be built as (10 - 3) - 2. The loop in term makes the previous node the left child of each new node — and that's exactly what creates left-associativity of operators.
# Stage Five — Interpreter: Give the Tree Meaning
let x = 10; stores the value 10 under the key x. Every time a variable node x is evaluated, the interpreter reads the current value from that same dictionary. If the name doesn't exist, we raise a runtime error instead of guessing a value.
false and the number zero are falsy, while true and any nonzero number are truthy. Arithmetic operators only accept numbers; since Python's Boolean is a subclass of int, we deliberately reject Booleans separately so an expression like true + 1 doesn't accidentally become valid in MiniLang.
while loop the condition must be re-evaluated on every iteration. If we computed the condition's value only once before the loop, changing the variable n inside the body would have no effect and the factorial loop would never terminate.
# Stage Six — Wire the Pieces Together
run function executes the article's pipeline in three lines: tokenize, parse, interpret. The command-line part reads the input file as UTF-8 and reports expected errors without dumping a long traceback:
minilang.py and save the program from the start of the article as factorial.mini:
n and result, tested the loop condition several times, performed the multiplications and assignments, and finally executed the correct branch of the if.
# What Happens When the Program Is Wrong?
print @; — the Lexer can't tokenize @let x = 10 — the ; is missingprint 1 / 0; — the structure is valid, but execution is impossible1 / 0 is perfectly valid grammatically; the problem only surfaces when the Interpreter tries to run it. In contrast, a missing ; is caught before execution, during the parse stage.
# What Does This Language Still Lack?
Lexer, grammar, recursive-descent parser, AST, variables, arithmetic, conditionals, loops, output, and errors.
Strings, functions, scopes, arrays, modules, a type checker, bytecode, an optimizer, a garbage collector, and native code.
if survives outside it. We also have no separate semantic-analysis stage, so errors like undefined variables are detected at runtime. These aren't hidden bugs — they're the project's simplifying decisions.
return.
# Why Is Building a Small Language Worth It?
while means repetition, and where a variable name points. But none of that is natural — they're all conventions the language's creator defined and implemented.
while. We built a program that agreed on what behavior to perform when it sees one. A programming language is fundamentally a precise contract between a human and an implementation — a contract that, if it has the slightest ambiguity, makes programs unpredictable.
only the program we wrote agreed on what to do when it sees one.
That's what a programming language is:
a precise contract that gives plain text its meaning.
