Python practice in Compilers & Interpreters
Browse the rooms before signing in. All rooms are free with an account. Practice does not issue certificates.
Classify the Character
Every scanner begins by asking what it is looking at. Decide whether a single character is a digit, the first question the lexer asks of every byte.
characters. Free with an account.
Name the Operator
The parser branches on token TYPES, not raw characters. Map a punctuation character to its operator name, the lexer's little dictionary.
operators. Free with an account.
Scan the Line
Put the scanner together: turn a line of source into a list of tokens, skipping the spaces that carry no meaning. The front door of every interpreter.
scanner. Free with an account.
Spot the Keyword
Names and reserved words look identical until the word is complete. Decide whether a finished word is one of the language's keywords.
words. Free with an account.
Apply the Operator
The evaluator's smallest step: given an operator and two numbers, compute the result. The atom of every arithmetic answer.
evaluate. Free with an account.
Print the Tree
To debug a parser you read its trees back. Pretty-print an AST as a Lisp-style s-expression, so '1 + 2 * 3' becomes (+ 1 (* 2 3)).
printer. Free with an account.
Rank the Operators
Pratt parsing replaces a function-per-level with one number per operator. Return an operator's binding power, the key that makes precedence work in a single loop.
pratt. Free with an account.
Walk the Tree
An expression is a tree; its value is found by walking it. Evaluate a binary AST node by evaluating its children and combining them, the heart of a tree-walker.
evaluate. Free with an account.
Decide the Truth
Every language draws its own line between true and false. Implement this one's rule: only nil and false are falsey, everything else (even 0) is truthy.
truthiness. Free with an account.
Desugar the Loop
A for loop is not a new idea, just sugar. Rewrite it as an initializer, a while, and an increment, so the existing machinery runs it.
control-flow. Free with an account.
Look It Up
Variables live in a chain of scopes. Resolve a name by walking the environment from innermost to outermost, the lookup that makes shadowing work.
environment. Free with an account.
Run the Loop
Loops compute real results. Run a counting loop that sums 1 through n, the kind of program your interpreter exists to execute.
control-flow. Free with an account.
Bind the Arguments
Calling a function opens a new scope binding each parameter to its argument. Build that frame, pushing a fresh scope onto the function's closure.
functions. Free with an account.
Call Yourself
Recursion works because a function can find its own name in the scope it captured. Compute a factorial recursively, the canonical proof that it works.
functions. Free with an account.
Recover from the Error
After a syntax error the parser cannot trust the next tokens, so it skips to a statement boundary and resumes. Synchronize the cursor past the next semicolon.
resolving. Free with an account.
Resolve the Depth
Before running, the resolver pins each variable to a scope distance. Return how many scopes out a name lives, or -1 if it is global.
resolving. Free with an account.
Compile to Bytecode
The second backend: walk the AST in post-order, emitting instructions so both operands sit on the stack before their operator runs. Compile an arithmetic tree.
bytecode. Free with an account.
Recurse for Real
The whole language at work: compute Fibonacci with a doubly-recursive function, the program that exercises calls, returns, and the call stack all at once.
mini-language. Free with an account.
Run the Machine
Execute bytecode on a stack VM: constants push, arithmetic pops two and pushes one. Run a whole chunk and return the value left on top.
bytecode. Free with an account.
Ship the Interpreter
The finale: a self-contained interpreter that runs a counting program. The whole pipeline, lex, parse, evaluate, in one function that returns the printed output.
mini-language. Free with an account.